Generated banner for Temporal Serverless
Generated logo for Temporal Serverless

Temporal Serverless

OpenAIOfficialCreated by TemporalDistributed by OpenAI

  • Skill
  • Workflow
  • Cloud and DevOps
  • MIT
  • Low risk

From the author

Deploy and operate Temporal Workers on serverless compute (AWS Lambda) driven by the Worker Controller Instance (WCI). Use when the user mentions: "serverless worker", "Temporal serverless", "Worker Controller Instance", "WCI", "deploy Temporal worker on Lambda", "Lambda packaging", "Lambda timeout", "WCI inspection", "CloudFormation Temporal".
— the description by Temporal

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

This skill provides a structured workflow for deploying and operating Temporal Workers on AWS Lambda. By utilizing the Worker Controller Instance (WCI), the skill enables serverless execution where Workers scale to zero when idle and are invoked on-demand by Temporal. The process covers the entire lifecycle, including code preparation, infrastructure deployment, IAM configuration, and verification, while providing diagnostic support for common serverless deployment issues.

What it does

  • Generates Worker code and deployment configurations for AWS Lambda.
  • Automates the creation of infrastructure stacks and IAM roles for secure invocation.
  • Registers and manages Worker Deployment Versions within Temporal.
  • Provides a step-by-step checklist to track deployment progress.
  • Offers diagnostic procedures to troubleshoot invocation and task processing issues.
  • Delivers a comprehensive inventory of created resources with optional teardown.

When to use it

  • When you need to deploy a Temporal Worker on AWS Lambda.
  • When you want to implement a serverless architecture for your Temporal Workers.
  • When you need to troubleshoot a Worker that is not being invoked by the WCI.
  • When you need to configure or update an existing Temporal serverless deployment.

Advantages

  • Enables cost-efficient scaling by allowing Workers to scale to zero when idle.
  • Provides a standardized, repeatable deployment process for serverless environments.
  • Includes built-in preflight checks to prevent common permission and configuration errors.
  • Maintains clear visibility into resource creation and lifecycle management.

Usage examples

“I need to deploy a new Temporal worker on AWS Lambda.”
The agent initiates the deployment workflow, starting with scoping questions to confirm your SDK, Namespace, and naming prefix before proceeding to infrastructure setup.
“My Temporal serverless worker is not picking up tasks, can you help?”
The agent guides you through the diagnostic decision tree, checking Lambda invocation logs and validating the connection between Temporal and your AWS Lambda function.

Install

  1. Download the skill as a ZIP:

    https://mp-staging.autogineer.com/agent-skills/openai/temporal-serverless/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip temporal-serverless.zip -d .agents/skills

Documentation by Temporal

Skill: temporal-serverless

Overview

This skill helps users deploy and operate Temporal Workers on serverless compute. Instead of a long-lived process, Temporal invokes the Worker on demand through the Worker Controller Instance (WCI); the Worker processes available Tasks and shuts down, scaling to zero when idle. The skill produces Worker code, deployment configuration, connection configs, and packaging steps for the chosen SDK, and walks users through troubleshooting when serverless Workers aren't picking up Tasks.

Supported compute providers

Cloud providerCompute serviceSupportReference directory
AWSLambdaSupported — Public Preview, open to all Temporal Cloud customersreferences/aws-lambda/
GCPCloud RunNot supported—

Only a provider marked Supported is covered. If a request names another, say it is not supported and stop; do not adapt a supported provider's material to it. Never let the provider be an unstated assumption: when the request does not name one, it is confirmed in the step 1 questions, not silently defaulted.

Every supported provider's directory carries the same layout — setup.md, iam.md, versioning.md, diagnostics.md, observability.md, self-hosted.md. Paths below are written references/<provider>/…; substitute the directory from the table. Provider-specific commands, templates, permissions, and defaults live there — this file stays at the workflow level. When a step needs concrete commands, go to the reference file named at the end of that step.

Public Preview is not GA. The APIs are still evolving and may change: pin SDK and CLI versions for anything long-lived, and read the installed package's actual API surface rather than writing from memory.

Deployment workflow

Follow these steps in order. Each step is provider-neutral; the concrete commands, templates, and options live in the reference file named at the end of the step.

Open a new deployment with a plain-language summary of the run. Before the step 1 questions, tell the user in a few sentences what is about to happen: that this creates real resources in their cloud account which cost money for as long as they exist; that you will ask about a handful of things, then show an exact list of what you are about to create and wait for approval, and that nothing is created before that approval; that the middle of the run is unattended; and that it ends with a Workflow they can watch execute, an inventory of everything created, and an offer to remove it all. Name the five stages below in ordinary words. Do not explain Temporal or serverless compute; keep it short enough to read at a glance.

Lay it out as bullets, with the five stages as sub-bullets under "How it goes" — one stage per line, never chained into a single run-on bullet. Follow this shape:

Here's what's about to happen, before I ask anything:

  • This creates real resources in your cloud account — the compute unit that runs your Worker, roles, an infrastructure stack, logs. They're live and billable for as long as they exist.

  • How it goes. Five stages:

    • Scope — a handful of questions, below.

    • Access — check credentials and permissions on both sides, then show you an exact list of what I'm about to create and wait for your approval.

    • Build — write, package, deploy the Worker.

    • Connect — bind the Task Queue, set the version current.

    • Verify and hand back.

  • Nothing gets created before you approve that list. After approval the middle stretch runs unattended.

  • At the end you get a Workflow you can watch execute, a full inventory of everything created, and an offer to remove it all.

Write the summary provider-neutral, because at that point you do not know the provider. It is one of the things step 1 asks. Say "your cloud account", never the name of a provider you have not been told. The same applies to the account, Namespace, and region: if a cheap read-only call has already told you (see step 1), name what you actually found; otherwise leave it out rather than filling it in with a plausible guess.

Skip the summary for troubleshooting, inspection, and configuration-change tasks. Someone whose Worker is not being invoked does not need an overview of a deployment they have already done.

Then track the run on a checklist, and reprint it every time a step completes. The eight steps group into the five stages below. Create one item per step, grouped under its stage, and build the checklist as soon as step 1's answers land, so items can name the confirmed provider and the agreed prefix instead of hedging.

Reprint the whole checklist at each step boundary — not just the item that changed, and not a sentence saying the stage is done. Mark finished items ✅, the one you are starting ⏳, and the rest ⬜. Use the bare marker with nothing in front of it — ✅ Confirm SDK, not - [x] Confirm SDK — and put each item on its own line. A narrated "Access complete, now Build" is not a substitute: it says where you are but not what remains, and the user cannot see it without scrolling back to a checklist printed twenty commands ago. Reprint during Scope and Access too — those stages end in a user decision, and the reprint is what shows the decision landed and what it unblocked.

Where the harness has a todo list, use it in addition to the printed checklist, not instead of it. It is not part of the transcript the user reads back.

Word each item as plain language about what happens, not as a compressed step title, and name both sides concretely — the confirmed compute provider and Temporal, never "both sides." Follow this shape:

Scope ✅ Confirm SDK (Go), compute provider (AWS Lambda), Namespace (<ns>), and naming prefix (<prefix>)

Access ⏳ Check credentials and permissions on AWS and on Temporal, then show the exact list of resources to be created and wait for your approval

Build ⬜ Write the Worker against the installed package's real API ⬜ Cross-compile, package, deploy the compute unit, wait for it to report ready

Connect ⬜ Create the role Temporal assumes to invoke the Worker ⬜ Register the Worker Deployment Version, confirm the validation invocation bound the Task Queue, set it current

Verify and hand back ⬜ Start a Workflow and confirm it executes, from both the Temporal side and the provider's logs ⬜ Deliver the inventory of everything created, then offer teardown

StageStepsComplete when
Scope1SDK, compute provider, Namespace, and naming prefix are all confirmed by the user.
Access2Compute provider and Temporal both authenticated, permissions confirmed, and the list of resources to create approved.
Build3–4The compute unit is deployed and reports ready, built for the architecture it runs on.
Connect5–6The Task Queue is bound and the version is current.
Verify and hand back7–8A Workflow completed, two independent signals agree, the inventory is delivered, and teardown has been offered.

A step is complete when its verification passed — not when its command exited zero. Several commands in this workflow exit clean having done nothing: the traffic-shifting and key-revocation commands no-op when their confirmation prompt goes unanswered, and providers return from create and update calls while the resource is still settling. Check an item off against state you read back, not against an exit code. When a step's verification fails, say which step you are on and what it is blocked on rather than moving down the list.

  1. Scope the task. Identify the SDK language (Go, Python, or TypeScript), the deployment target (Temporal Cloud or self-hosted — self-hosted has its own server prerequisites), the compute provider, and whether this is a new setup, a configuration change, or troubleshooting. Confirm the deployment target is compatible with the chosen provider — see "A Namespace on the target cloud provider is required" under Provider-neutral principles. Ensure a Temporal client/CLI is available and authenticated to the target. Each changes the specifics. → references/concepts.md for what the user is building; references/<provider>/setup.md for the compatibility and client-setup details.

    Put the compute provider in that batch of questions as a confirmable default, not a free choice. Pre-select the supported provider from the table above and carry its support status in the option's description. The user confirms rather than chooses, so it costs no extra turn, but the provider is never something they were assumed into. Skip the question only when the request already names a provider. Do not restate any of this in a paragraph before the questions; the option description is where it belongs.

    Let the user pick the Namespace from a list; never make them retype one. Namespace names are long and error-prone — a generated suffix on an account ID, <name>-<suffix>.<account>. Where control-plane access is available, tcld namespace list returns the full Namespace objects, so one call gives every name with its region — and a region ID is provider-prefixed (aws-…, gcp-…), so the same response tells you each Namespace's provider. Only the prefix carries meaning; the region itself imposes no constraint.

    Present it like this:

    • Offer the eligible Namespaces as the options, each labelled with its region.

    • Summarize the ineligible ones in a single line — "you also have 2 Namespaces on <provider>, which this skill does not support" — rather than listing them individually or hiding them. A user who knows they have a Namespace and cannot find it in the list concludes the tool is broken; one line keeps them informed and explains the constraint.

    • Name the account you are listing from and confirm it is the intended one before showing anything. A stale credential lists a real account that is not the one the user means to deploy into, and every option under it looks authoritative.

    • If more Namespaces are eligible than the question format can hold, print the labelled list and ask the user to name one. Do not silently show only the first few.

    This also settles the compute-provider answer, since a Namespace can only be served by compute on its own cloud provider — so a mismatch is caught here rather than at connection time, several steps later.

    Degrade gracefully if tcld is not authenticated. Ask the user for the Namespace name rather than stopping to fix the login — they can copy it from the Cloud UI, where it appears on the Namespace page and in the URL. Ask for its region in the same batch of questions: the name alone does not tell you the provider, and a mismatch missed here surfaces at connection time instead.

    Never source a Namespace, account, or resource identifier from shell history. History is stale by construction — it is full of last quarter's accounts — and reading it to guess a deployment target produces confident, wrong answers. Take identifiers from the user or from an authenticated API call, and nowhere else.

    Agree a resource-naming prefix in this same batch of questions, and propose a default so the user can accept without thinking about it. Assume the account and the Namespace are shared — unprefixed names like temporal-serverless-worker collide with, or quietly shadow, another team's deployment. Naming is not a late cosmetic choice you can patch on the provider side: the deployment name, build ID, and Task Queue are compiled into the Worker binary, so changing them after step 3 means editing code, rebuilding, repackaging, and cleaning up whatever was already created under the old names. Once agreed, apply the prefix to everything you create on both sides — compute unit, roles, infrastructure stacks, log groups, deployment name, and Task Queue.

    The prefix you propose must be identifying — derived from the user, their team, or the project. A generic word like demo, test, or temporal collides about as readily as no prefix at all, so never offer one as the safe choice.

    Offer exactly two options plus the free-text escape: the identifying prefix, and "no prefix" — some users genuinely own the account. Do not offer a second prefix string; the consequential choice is prefix versus none, and anything else goes in free text. When you offer "no prefix," say what it risks in the same breath: unprefixed names can collide with or shadow an existing deployment, and that surfaces as another team's Worker behaving oddly rather than as an error you will see.

  2. Confirm you can make the required changes — before making any. Determine which credentials are available (for the compute provider and for Temporal) and confirm the active identity actually has permission to make the changes the task needs — creating or updating compute resources, creating roles, registering deployment versions. Verify both sides: the compute provider AND Temporal access. Do not run account-mutating commands and let them fail partway. If access is missing or unconfirmed, stop and ask the user how they want to proceed — extend their identity's permissions, have an administrator make the change and hand back the result, or generate the commands for the user to run under a privileged identity. Changing a user's cloud account is consequential; confirm authorization and the preferred method first. → references/<provider>/iam.md (exact permissions, compute-provider preflight) and references/<provider>/setup.md (Temporal connection preflight).

    Classify an authentication failure before acting on it — "not signed in" and "not permitted" have different fixes. A failed preflight does not automatically mean the bottom row of the table below. An absent or expired credential is usually recoverable in this session, in under a minute. A caller that resolves but is denied a specific action is a real permissions problem. Never collect credentials in the conversation: no interactive credential-configuration wizards, and never ask the user to paste access keys, API keys, or session tokens. → references/<provider>/iam.md (credential recovery).

    Then ask the user which way they want to go, and do not choose for them:

    • Fix the CLI — you run the login flow, surface the verification URL for them to open, wait for it to complete, re-run the preflight, and continue with full automation.

    • Work in the browser — the user makes the changes in the Cloud UI and their cloud provider's console while you give the instructions step by step, naming the exact path for each one, and they report the result back.

    Both paths reach the same end state, so present them as equals rather than as a preference and a fallback — every control-plane step in this workflow exists in the Cloud UI (see references/<provider>/setup.md). Ask once, then commit to the answer — do not re-offer the login at every subsequent step, and never start an identity-provider login as a silent side effect of a preflight.

    Adapt to what is available — the skill is valuable at every level:

    Compute-provider accessTemporal accessBehavior
    AuthenticatedAuthenticatedFull workflow — run commands, verify results, register deployment versions.
    AuthenticatedNoneDeploy compute infrastructure; walk the user through the Temporal steps in the Cloud UI, or generate the commands for them to run.
    NoneAuthenticatedWrite Worker code and configs; walk the user through the compute steps in their provider's console, or generate the deploy commands; run Temporal commands and verify WCI state.
    NoneNoneWrite Worker code, deploy templates, permission policies, connection configs, packaging scripts; provide all commands with placeholder values.

    Do not self-select a row. Drop to a lower one only after the choice above has been put to the user and the browser path chosen, or the login attempted and failed. When you hand off a runbook, say the offer stands — if the user authenticates and comes back, take the work over rather than leaving them to run the steps by hand.

    Before the first account-mutating command, list what you are about to create — with final names — and get approval. Name the target account and region, then every resource: compute unit, execution role, infrastructure stack, log group, deployment name, and Task Queue. Say plainly that they are live and billable. This is the mirror of the inventory in step 8, and it is worth more here than there: it makes the naming prefix concrete while changing it is still free, and the deployment name, build ID, and Task Queue become expensive to change once step 3 compiles them into the Worker. Skip it only when nothing will be created — a troubleshooting or inspection task.

  3. Author the Worker. Install the SDK's serverless Worker package before writing any code — it is often shipped separately from the main SDK, with its own version line, so having the base SDK installed does not mean it is importable. Then read the installed package's actual API surface and write against that; these are Public Preview APIs that drift between versions, and generating code from memory costs a build cycle. Every Workflow must declare a versioning behavior (Pinned or AutoUpgrade), per-Workflow or as a Worker-level default — code without it fails at runtime. → references/sdk-configuration.md (package, install, entry point, tuned defaults) and references/<provider>/setup.md (install commands, API-inspection recipes, handler shape).

  4. Package and deploy the compute unit. Build and package per SDK, deploy the compute unit, and set the invocation deadline high enough for the Worker to start, connect, register the Task Queue, and shut down gracefully. Match the build's target architecture to the deployed compute unit's — a mismatch fails only at invocation time, not at build time. After a create or update, wait for the compute unit to reach a ready state before the next step; providers return from these calls while the unit is still settling. → references/<provider>/setup.md.

  5. Grant Temporal permission to invoke the Worker. Configure the compute provider's access so Temporal can invoke and inspect the Worker. This access is separate from the compute unit's own execution role — do not confuse the two. Two things to get right before you create anything: (a) this grant is shared, account-wide infrastructure that a previous deployment may already have created — look for an existing one and extend it to cover your new Worker rather than creating a parallel copy, and never delete or repurpose one you did not create without asking; (b) scope the grant so that future immutable builds are covered, not just today's — a grant pinned to one build breaks the next release in a way that surfaces later as an unrelated-looking invocation failure. → references/<provider>/iam.md.

  6. Register the Worker Deployment Version, verify the validation invocation, then set it current. Create the Worker Deployment Version with the compute provider configured; the deployment name and build ID must exactly match the values in the Worker code. Creating it triggers one validation invocation — check that it bound the Task Queue before going further. If the Task Queue is bound, the permission grant, package, config, and deadline are all provably correct, and any later failure is downstream; if it is not, setting the version current will not fix it. Then set it current: through the UI this happens automatically, through the CLI it is a separate step, without which Tasks never route to the version. → references/<provider>/setup.md.

  7. Verify. Start a Workflow on the Task Queue and confirm Temporal invokes the Worker — check the Workflow history in the Temporal UI and the compute provider's logs. If it does not progress, → references/<provider>/diagnostics.md.

  8. Hand back the inventory first; offer teardown as the closing note. The order is inventory → offer, never the reverse. Close with what now exists — compute unit and published build identifiers, roles, infrastructure stacks, region, deployment name and build ID — and what the run actually did, including anything you worked around or deviated from. Say plainly that it is live and billable. These names are only knowable from the run that created them, and reconstructing them later means scanning the user's account.

    Do not write a teardown script before the user asks for one. Generating it unprompted buries the inventory under a file they did not request, and the inventory is what they need in order to decide. End with a single line — "Let me know if you want a teardown script to remove these resources" — and stop there. Write the script, or run the teardown, when they take you up on it. → references/<provider>/setup.md (Teardown).

Working practices

How to move through the workflow above.

  • Say what a command will do in your own text, above the command. The user sees a collapsed "Ran 6 shell commands" in the transcript, not the commands themselves, so an unannounced batch is opaque at exactly the moments that matter. State it in one line before the tool call: what it does and to what — the resource, and the account or Namespace it touches.

    The tool's own description field does not count. It renders at the bottom of the command block, underneath the command it is describing, where the user has to go looking for it. The summary belongs above the block, as ordinary message text.

    Set it apart on its own line so it is visibly not narrative prose — bold, and nothing else on the line:

    Checking for the Temporal CLIs, AWS credentials, and which AWS account and Temporal account they resolve to

    Creating the invocation role Temporal assumes, with a generated External ID

    For anything that creates, updates, or deletes, name the resource and the target account or Namespace explicitly — an approval prompt should arrive with its justification already on screen, not after it.

  • Read the current state instead of recalling it. Check the installed package's API, the CLI's own --help for the flags you are about to pass, the compute unit's reported state, and the CLI version. Each of these has drifted in practice: a Public Preview SDK whose fields moved, a CLI too old to have the serverless subcommand at all, a resource that reports success while still settling.

  • Do not chain cd with commands that create or modify files. A compound cd <dir> && <write> triggers a manual approval prompt no matter how the user's permissions are configured, so scaffolding a project this way asks for approval on every run. Use absolute paths, or the tool's own directory flag (go -C <dir> …), and rely on the shell's working directory persisting between calls — the cd buys nothing and costs a prompt. Keep the command count down for the same reason: one go get covering both packages beats two.

  • Verify each step before building the next on top of it. Compile the Worker before packaging it, confirm the package's target architecture before uploading, wait for the compute unit to be ready before publishing a build, and confirm the Task Queue is bound before shifting traffic. Deployment failures here surface far from their cause — an architecture or dependency mismatch appears only at first invocation, and a first-invocation failure appears as "the Worker is never invoked", several steps later.

  • When something fails, read the actual error before changing anything. Fetch the failure reason from the provider (deployment events, logs, status fields) and fix that. Do not retry the same command with variations, and do not start editing permissions or trust policies on the theory that the problem might be access — most first-invocation failures are not permission problems, and some failures are on Temporal's side and will reproduce no matter what you change.

  • Treat the user's account as shared and pre-existing. Assume other deployments, roles, and stacks are already there. Look before creating, extend rather than duplicate, and never delete or repurpose something you did not create without asking. When you do work around existing infrastructure — a different name, a reused role — say so explicitly in your summary rather than leaving it as a silent deviation.

  • Confirm the end state from two independent signals. A Workflow that completes in the Temporal UI and the Worker's own logs showing startup, Task Queue registration, and Task execution. One signal alone can mislead: a system Workflow that exists and is running proves nothing about invocation health, and a command that exits zero may have done nothing at all if it was waiting on a confirmation prompt.

  • Account for what you created. Keep the inventory as you go rather than reconstructing it at the end, say plainly that the resources are live and billable, and offer to tear them down (step 8).

Never create or manage the WCI

Temporal creates the WCI automatically once a Worker Deployment Version has a compute provider. You never create, start, or manage it. A WCI that exists or is running is not evidence that invocation works — it continue-as-news and keeps running even while its Activities fail. Diagnose from Temporal's own signals: read the WCI Workflow history and look for Activity failures. Do not enumerate compute resources across regions or scan the account to reverse-engineer state. → references/concepts.md, references/<provider>/diagnostics.md.

Provider-neutral principles

Surface these early — they apply regardless of compute provider:

  • A Namespace on the target cloud provider is required. A Serverless Worker runs only on the cloud provider that hosts its Temporal Cloud Namespace — there is no cross-cloud pairing. Confirm the user has a Namespace on the provider they intend to run compute on before building anything; without one, the work stops there and they need either a Namespace on that provider or a different provider. A mismatch is not caught at deploy time — it fails later, at connection time. Regions do not have to match: a Namespace in one region can drive a compute unit in another, so never tell a user to move or re-create a Namespace to line up regions.

  • Use tcld for every Temporal Cloud control-plane operation — accounts, Namespaces, API keys, users, service accounts. Do not use the unified CLI's temporal cloud … subcommands for them. Worker Deployments and Workflows are not control-plane operations: they live on the Namespace frontend, have no tcld equivalent, and use temporal worker deployment …. → references/<provider>/setup.md.

  • Versioning behavior is mandatory. Every Workflow needs Pinned or AutoUpgrade, or the Worker sets a default.

  • Deployment name and build ID must match exactly between the Worker code and the Worker Deployment Version. A mismatch causes an invocation loop (Temporal invokes → Worker polls with the wrong version → Task not processed → invoke again). Signature: rapid repeated invocations with no Workflow progress.

  • Set the invocation deadline high enough. Providers often default to a very short timeout. If the first invocation times out before the Worker registers the Task Queue, the binding is never created and the Worker is never invoked again. → references/<provider>/setup.md for the exact default.

  • Use an immutable, versioned build per Build ID in production. Pointing the provider at a mutable "latest" target lets code change under in-flight Workflows and cause non-determinism errors, even for Pinned Workflows. Keep a 1-to-1 mapping between each Build ID and one immutable build. → references/<provider>/versioning.md.

  • Tune the timeout triple together for long-running Activities: (1) worker stop timeout > longest Activity runtime, (2) shutdown deadline buffer > worker stop timeout + shutdown hook time, (3) invocation deadline > longest Activity runtime + shutdown deadline buffer. Raising one alone does not help. If the longest Activity exceeds half the maximum invocation deadline, recommend Activity Heartbeats. → references/concepts.md, references/sdk-configuration.md.

  • Eager Activities are always disabled — serverless invocations don't maintain persistent connections. Don't suggest them as an optimization.

  • Activities are bounded by the invocation limit (minus the shutdown deadline buffer); Workflow duration is unbounded and can span many invocations. Flag Activities that approach the provider's limit early. → references/concepts.md.

  • Mixed serverless + long-lived Workers on one Task Queue: do not enable dynamic scaling on the long-lived Workers — the two groups can't coordinate scaling and will cause unnecessary invocations.

  • Secrets belong in a secret store, not plaintext environment variables. Provider docs and quickstarts commonly pass the API key or TLS key as a plaintext environment variable; that is acceptable in a throwaway development walkthrough only if you say so explicitly at the time. Anything the user describes as production, shared, or long-lived gets the secret store, loaded at cold start. Either way, keep key material out of shell history and command echoes.

  • Both CLIs prompt for confirmation before mutating state, and their flags differ. Setting the current or ramping version, and revoking an API key, all ask interactively; run non-interactively without the flag, the command exits having done nothing, which reads as success. temporal worker deployment … takes --yes; tcld takes the global --auto_confirm. Pass the right one in scripts, CI, and agent shells, and confirm the resulting state rather than trusting the exit code. → references/<provider>/setup.md.

Troubleshooting

Start by determining whether the Worker is being invoked at all. Then, in priority order: (1) Validate Connection in the Temporal UI (Workers > Deployments > select > Actions > Validate Connection) — checks credentials, role assumption, and reachability in one step; (2) check whether the version's Task Queue is bound — if it is, invocation and Worker startup provably work and the fault is downstream, which rules out most of the surface in one command; (3) confirm the version is current (CLI-created versions are not automatic, and a confirmation-prompted command may have silently done nothing); (4) check the compute provider's logs for connection, auth, or TLS errors; (5) if rapid repeated invocations show no progress, check the deployment name/build ID match. Distinguish a Temporal-side failure (reproduces no matter what you change on the provider side) from a genuine user-permission problem before editing anything. → references/<provider>/diagnostics.md, references/concepts.md.

Common Pitfalls

High-impact mistakes — warn the user proactively. Each is a symptom → cause → fix.

  1. Deployment name / build ID mismatch → invocation loop. Symptom: rapid, repeated invocations with no Workflow progress. Cause: the name or build ID in the Worker code doesn't match the Worker Deployment Version, so the Worker polls with the wrong version, the Task isn't processed, and Temporal invokes again. Fix: make the values in code exactly match the version configuration.

  2. Version not set as current. A version created through the CLI is not automatically current; without it, Tasks don't route to the version and the Worker is never invoked. Fix: set it current as a separate step (the UI does this automatically).

  3. Failed first invocation. When a version is created, the WCI invokes the Worker once to validate. If that invocation fails — missing env vars, bad TLS/auth config, missing dependencies, or an invocation deadline too short for the Worker to start and register the Task Queue — the Worker never connects, never polls, the binding is never created, and the Worker is never automatically invoked again. Fix: diagnose by manually invoking the compute unit, and confirm the invocation deadline is set high.

  4. Confusing the two roles. The compute unit's execution role (grants the function permission to run) is separate from the access Temporal uses to invoke it. Never describe one as the other. → references/<provider>/iam.md.

  5. Timeout tuning mismatch. Raising only the shutdown deadline buffer makes the Worker stop polling earlier but gives in-flight Activities no more time; raising only the worker stop timeout doesn't make it stop polling earlier, so the provider may terminate the Worker first. Fix: tune the three values together (see the timeout triple above).

  6. Mutable "latest" build reference in production. Pointing the provider at a mutable/unqualified target means the code changes on every redeploy; deploying replay-unsafe code then causes non-determinism errors for in-flight Workflows, even Pinned ones. Fix: publish an immutable versioned build and keep a 1-to-1 mapping between each Build ID and one build. → references/<provider>/versioning.md.

  7. Re-creating shared permission infrastructure that already exists. Symptom: the infrastructure deployment fails outright and rolls back, or it succeeds and leaves a second, redundant grant behind. Cause: the permission grant Temporal assumes is account-wide with a fixed default name, so a previous serverless deployment already owns it. Fix: check whether it exists and what owns it before creating; extend the existing one to cover the new Worker, and fall back to a distinctly named parallel one only when the existing infrastructure is not yours to change — saying why when you do. A failed-and-rolled-back deployment must be deleted before the name can be reused; a successful one is live infrastructure and must not be. → references/<provider>/iam.md.

  8. Invoke permission scoped to a single build. Symptom: the deployment works, then the next release cannot be invoked, with an error that looks like a connection or configuration problem rather than a permissions one. Cause: the grant named one immutable build, and the new release is a different resource. Fix: scope the grant to cover the base resource and all its published builds. → references/<provider>/iam.md.

Routing to reference files

Most questions need 2–3 reference files.

User intentReference file(s)
What is a Serverless Worker / the WCI? How do invocation and autoscaling work? What are the constraints? Serverless vs long-lived Workers?references/concepts.md
Deploy a Serverless Worker (happy path): write code, package, deploy, register + set-current version, verify, tear down.references/<provider>/setup.md (+ references/concepts.md)
Operator permissions and preflight; execution role vs Temporal invocation role; CloudFormation (Cloud + self-hosted).references/<provider>/iam.md
Update or redeploy; version the build, use a qualified ARN, roll back.references/<provider>/versioning.md (+ references/concepts.md)
Self-hosted server enablement (dynamic config, WCI, server AWS credentials).references/<provider>/self-hosted.md (+ references/<provider>/iam.md)
SDK-specific options and tuned defaults, which package to install and how it is distributed, imports, versioning-behavior configuration, connection config (TOML, env vars). Reduce cold start / pre-bundle Workflow code.references/sdk-configuration.md
Add OpenTelemetry observability, collector config, tracing.references/<provider>/observability.md
Worker not invoked, Workflows not progressing, inspect the WCI.references/<provider>/diagnostics.md (+ references/concepts.md)
Long-running Activities and timeout relationships. Isolate Activities from resource exhaustion.references/concepts.md (+ references/sdk-configuration.md)

Out of Scope

  • General SDK development patterns (Workflows, Activities, signals, queries, Worker Versioning concepts): see skill-temporal-developer.

  • Traditional Worker tuning (slot suppliers, tuners, poller autoscaling, resource-based tuning): see skill-temporal-workertuning.

  • Temporal Cloud administration (Namespaces, users, certificates, billing): see skill-temporal-ops.

  • CLI command reference (beyond the serverless-specific flags): see skill-temporal-cli.

Files

references/aws-lambda/setup.md

AWS Lambda — Setup (happy path)

<!-- Sources: docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx docs/production-deployment/worker-deployments/serverless-workers/index.mdx docs/develop/environment-configuration.mdx -->

This is the end-to-end golden path: connect, write the Worker, package and deploy, register a Worker Deployment Version, set it current, and verify. For the operator permissions and preflight, execution/invocation roles, and CloudFormation, see iam.md. For production build versioning (publish-version, qualified ARNs, rollback), see versioning.md. For self-hosted server enablement, see self-hosted.md. If it doesn't work, see diagnostics.md.

Prerequisites

  • A Temporal Cloud account with an AWS-hosted Namespace, or a self-hosted Temporal Service v1.31.0 or later. <!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:37 -->

  • The Namespace's cloud provider must match the serverless compute provider. <!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:37-38 -->

  • For self-hosted deployments, complete the self-hosted setup before following the deployment guide. <!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:39-41 -->

  • Every Workflow must declare a versioning behavior, or the Worker must set a default versioning behavior. <!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:42-43 -->

  • An AWS account with permissions to create and invoke Lambda functions and create IAM roles. For the exact operator actions and a preflight check, see iam.md. <!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:44 -->

  • The AWS-specific steps require the aws CLI installed and configured with your AWS credentials. You may also use the AWS Console or the AWS SDKs. <!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:45-46 -->

  • The Go SDK, Python SDK, or TypeScript SDK, depending on your language. <!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:48-49 -->

  • The temporal CLI, authenticated to the target Temporal Service — Steps 4–6 and the CLI troubleshooting paths use it. See "Temporal CLI and Cloud connection" below.

Sample projects:

Temporal CLI and Cloud connection

Steps 4–6 and the CLI troubleshooting paths use the temporal CLI. Install it and authenticate it to the target Temporal Service before those steps, or commands default to localhost:7233 and fail against Temporal Cloud. The serverless worker deployment create-version subcommand and its --aws-lambda-* flags also require a recent CLI build — see "Check the CLI version" in Step 4. <!-- docs/develop/environment-configuration.mdx -->

Authenticate to Temporal Cloud (API key). Export environment variables (the CLI and the serverless Worker packages both read these):

export TEMPORAL_ADDRESS="<namespace_id>.<account_id>.tmprl.cloud:7233"
export TEMPORAL_NAMESPACE="<namespace_id>.<account_id>"
export TEMPORAL_API_KEY="<your-api-key>"

or configure a profile and pass --profile prod on each command:

temporal --profile prod config set --prop address --value "<namespace_id>.<account_id>.tmprl.cloud:7233"
temporal --profile prod config set --prop namespace --value "<namespace_id>.<account_id>"
temporal --profile prod config set --prop api_key --value "<your-api-key>"

<!-- docs/develop/environment-configuration.mdx:122-131 -->

or configure an environment and pass --env prod (or set TEMPORAL_ENV):

temporal env set --env prod --key address --value "<namespace_id>.<account_id>.tmprl.cloud:7233"
temporal env set --env prod --key namespace --value "<namespace_id>.<account_id>"
temporal env set --env prod --key api-key --value "<your-api-key>"

Do not assume which of the three a user has, and do not migrate them. --env (YAML, temporal env) is the long-standing mechanism; --profile (TOML, temporal config) is newer and the CLI still marks it EXPERIMENTAL. Both are supported — work with whichever is already configured. Read the existing values rather than asking the user to re-enter them:

temporal env get --env prod          # --env mechanism
temporal config get --prop address   # --profile mechanism
  • For Temporal Cloud the Namespace is the fully-qualified <namespace_id>.<account_id>, not the bare name. <!-- docs/develop/environment-configuration.mdx:128-129 -->

  • Supplying an API key auto-enables TLS; no cert flags are needed for API-key auth. <!-- docs/develop/environment-configuration.mdx:70 -->

  • The temporal ... commands in Steps 4–6 assume this is configured. To create an API key, see skill-temporal-ops.

Temporal-side preflight. Confirm the CLI can reach the Namespace before deploying — this is the Temporal side of the pre-deploy access check. It should list (empty is fine) without an auth or connection error:

temporal worker deployment list

If this fails with an auth error, note first that this is a frontend call — it needs address, Namespace, and an API key. A control-plane login does not provide any of them; the two planes are separate:

Control plane (accounts, Namespaces, API keys)Namespace frontend (Workflows, Worker Deployments)
Interactivetcld logintemporal ... with address + namespace
Headless--api-key / TEMPORAL_CLOUD_API_KEYTEMPORAL_API_KEY

Use tcld for every Temporal Cloud control-plane operation — accounts, Namespaces, API keys, users, service accounts. Do not use the unified CLI's temporal cloud … subcommands for them.

Worker Deployments and Workflows are not control-plane operations. They live on the Namespace frontend and have no tcld equivalent: Steps 4–6 use temporal worker deployment …, authenticated with address, Namespace, and TEMPORAL_API_KEY.

The two API-key variables are different: TEMPORAL_CLOUD_API_KEY authenticates tcld, TEMPORAL_API_KEY authenticates the frontend and is the one the Worker needs. Do not set one expecting the other.

Two tcld mechanics worth knowing before you run it in an agent shell:

  • tcld login --disable-pop-up prints the URL instead of opening a browser. Auto-open is unreliable over SSH, in containers, and in remote sessions, and the user needs the URL in the conversation either way.

  • tcld prompts for confirmation before mutating operations. Non-interactively, pass the global --auto_confirm (note the underscore) or set AUTO_CONFIRM=true, then read the resulting state back — without it the command exits clean having changed nothing.

Go to the API key first. It requires no CLI login, no browser handshake, and works on every account type:

export TEMPORAL_ADDRESS="<namespace_id>.<account_id>.tmprl.cloud:7233"
export TEMPORAL_NAMESPACE="<namespace_id>.<account_id>"
export TEMPORAL_API_KEY="<created in the Cloud UI>"

Have the user create the key in the Cloud UI, signing in however they normally do, and confirm the address against the endpoint shown on the Namespace page — some Namespaces have regional endpoints that do not follow the pattern above. Never ask them to paste the key into the conversation.

A control-plane login is a convenience, not a prerequisite. When it is available it saves asking:

tcld namespace list             # full Namespace objects — every name with its region and endpoint
tcld namespace get -n <ns>      # one Namespace
tcld apikey create --name <name> --duration <d>

apikey create mints a key for the calling user and creates a long-lived credential in their account — offer it and get explicit approval, never silently. tcld is not guaranteed present: check command -v tcld, and read tcld <group> --help for the flags you are about to pass.

When a control-plane login fails, stop — do not debug it, retry it, or install another CLI. Some accounts cannot complete a tcld login at all, and no flag, plugin upgrade, or alternate CLI changes that. Retrying burns turns without converging, and the browser path below reaches the same end state anyway.

Then put the choice to the user rather than deciding for them: fix the CLI, or work in the browser while you give the instructions. Only the control-plane steps move — the frontend work needs address, Namespace, and an API key, and no control-plane login at all, so it continues either way. In the Cloud UI, the control-plane steps are Namespace names, regions, and endpoints, and API key creation; the frontend steps are creating the Worker Deployment Version with its compute provider, and setting a version current, which the UI does automatically and the CLI does not. Where the login cannot complete, say so plainly instead of sending the user back to retry it.

Do not proceed to Steps 4–6 on the assumption auth will work — re-run this command and confirm.

Step 1: Write Worker code

The Worker handles the per-invocation lifecycle: connecting to Temporal, polling for tasks, and gracefully shutting down before the invocation deadline. <!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:62-63 -->

Install the serverless Worker package first

The serverless Worker package is not always part of the main SDK. Install it explicitly before writing code — do not assume an import resolves just because the base SDK is present. Scaffolding a project and discovering only at build time that the package lives in its own module means backing out and redoing the module setup.

SDKInstallPackaging
Gogo get go.temporal.io/sdk/contrib/aws/lambdaworkerSeparate Go module from go.temporal.io/sdk, with its own version line (v0.1.1 at the time of writing). It is not pulled in by the main SDK — go get it directly, then go mod tidy.
Pythonpip install temporaliotemporalio.contrib.aws.lambda_worker ships inside the main temporalio package. Use temporalio[lambda-worker-otel] to add OpenTelemetry.
TypeScriptnpm install @temporalio/lambda-workerSeparate npm package from @temporalio/worker, versioned independently.
Verify the installed API before generating code

These are Public Preview APIs and signatures drift between versions. Read the real surface of the version you just installed rather than writing from memory — a wrong field name costs a build cycle:

# Go — list the exported API of the installed module version
go doc go.temporal.io/sdk/contrib/aws/lambdaworker
go doc go.temporal.io/sdk/contrib/aws/lambdaworker.Options

# Python
python -c "import temporalio.contrib.aws.lambda_worker as m; help(m.LambdaWorkerConfig)"

# TypeScript — check the installed version, then read its type declarations
npm ls @temporalio/lambda-worker

Specifics worth confirming this way, because they differ by SDK and are easy to get wrong from memory:

  • Where the Task Queue lives. In Go it is a direct field on the options object (opts.TaskQueue). In Python it goes through the worker-config mapping (config.worker_config["task_queue"]), and in TypeScript through worker options (config.workerOptions.taskQueue). Do not carry one shape over to another language.

  • Where registration happens. In Go the Register* methods hang off the same options object; Python and TypeScript pass Workflow and Activity collections into the worker config.

  • You do not construct a client. Connection details (address, namespace, API key) load automatically from the process environment, so TEMPORAL_* variables set on the function flow straight through with no client code. In a Lambda that means the --environment block at deploy time: no config file is bundled unless you put one there, and the operator's own CLI configuration never reaches the function (see "Operator CLI config does not reach the function" below).

  • Worker Versioning is always on. The run-worker entry point enables it, so the only remaining decision is Pinned vs AutoUpgrade per Workflow (or a Worker-level default).

Fastest path: start from the language sample linked in Prerequisites — it has a working Worker, Workflow, and Activity already wired together. The handler examples below import the Workflow and Activity from separate modules (my_workflows, my_activities). When writing from scratch, create those modules with at least one registered Workflow (declaring a versioning behavior) and one Activity, and name the entry-point file to match the --handler you deploy (for example, lambda_function.py → --handler lambda_function.lambda_handler).

Go

Use the Go SDK's lambdaworker package. <!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:68 -->

package main

import (
    lambdaworker "go.temporal.io/sdk/contrib/aws/lambdaworker"
    "go.temporal.io/sdk/worker"
    "go.temporal.io/sdk/workflow"
)

func main() {
    lambdaworker.RunWorker(worker.WorkerDeploymentVersion{
        DeploymentName: "my-app",
        BuildID:        "build-1",
    }, func(opts *lambdaworker.Options) error {
        opts.TaskQueue = "my-task-queue"

        opts.RegisterWorkflowWithOptions(MyWorkflow, workflow.RegisterOptions{
            VersioningBehavior: workflow.VersioningBehaviorPinned,
        })
        opts.RegisterActivity(MyActivity)

        return nil
    })
}

<!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:70-94 -->

Versioning behavior: set per-Workflow at registration time with workflow.VersioningBehaviorPinned or workflow.VersioningBehaviorAutoUpgrade, or set a Worker-level default with DefaultVersioningBehavior in DeploymentOptions. <!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:96-98 -->

Python

Use the Python SDK's lambda_worker contrib package. <!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:106 -->

from temporalio.common import WorkerDeploymentVersion
from temporalio.contrib.aws.lambda_worker import LambdaWorkerConfig, run_worker

from my_workflows import MyWorkflow
from my_activities import my_activity


def configure(config: LambdaWorkerConfig) -> None:
    config.worker_config["task_queue"] = "my-task-queue"
    config.worker_config["workflows"] = [MyWorkflow]
    config.worker_config["activities"] = [my_activity]


lambda_handler = run_worker(
    WorkerDeploymentVersion(
        deployment_name="my-app",
        build_id="build-1",
    ),
    configure,
)

<!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:108-129 -->

Versioning behavior: set per-Workflow in the @workflow.defn decorator with VersioningBehavior.PINNED or VersioningBehavior.AUTO_UPGRADE, or set a Worker-level default with default_versioning_behavior in the worker config. <!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:131-133 -->

from temporalio import workflow
from temporalio.common import VersioningBehavior


@workflow.defn(versioning_behavior=VersioningBehavior.PINNED)
class MyWorkflow:
    @workflow.run
    async def run(self, input: str) -> str:
        ...

<!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:135-145 -->

TypeScript

Use the @temporalio/lambda-worker package. <!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:153 -->

import { runWorker } from '@temporalio/lambda-worker';
import * as activities from './activities';

export const handler = runWorker({ deploymentName: 'my-app', buildId: 'build-1' }, (config) => {
  config.workerOptions.taskQueue = 'my-task-queue';
  config.workerOptions.workflowBundle = {
    codePath: require.resolve('./workflow-bundle.js'),
  };
  config.workerOptions.activities = activities;
  config.workerOptions.workerDeploymentOptions!.defaultVersioningBehavior = 'PINNED';
});

<!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:155-167 -->

Use workflowBundle with pre-bundled code instead of workflowsPath to avoid webpack bundling overhead on Lambda cold starts. <!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:169-170 -->

Versioning behavior: set per-Workflow with setWorkflowOptions in the Workflow file, or set a default for all Workflows with defaultVersioningBehavior in the configure callback. Values are 'AUTO_UPGRADE' or 'PINNED'. <!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:172-174 -->

Step 2: Deploy Lambda function

Build and package
Go

Cross-compile for Lambda's Linux runtime: <!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:191 -->

GOOS=linux GOARCH=amd64 go build -tags lambda.norpc -o bootstrap ./worker

<!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:194 -->

Package the binary into a zip file: <!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:197 -->

zip function.zip bootstrap

<!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:200 -->

Add CGO_ENABLED=0, and match the architecture you deploy. The provided.al2023 runtime expects a self-contained binary; building with cgo enabled links against host libraries that may not resolve inside the runtime. Set CGO_ENABLED=0 for a statically linked binary, and keep GOARCH consistent with the function's --architectures (amd64 ↔ x86_64, arm64 ↔ arm64). Also adjust the trailing package path to your layout — . when main is in the repo root, ./worker when it is in a worker/ subdirectory. A reusable script:

#!/usr/bin/env bash
set -euo pipefail
go vet ./...     # catches a missing import before the cross-compile
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -tags lambda.norpc -o bootstrap .
zip -q function.zip bootstrap
file bootstrap   # expect: ELF 64-bit ... statically linked

Run go vet (or a plain go build ./...) before the packaging build. The three-package import block above — lambdaworker, worker for WorkerDeploymentVersion, and workflow for the versioning-behavior constants — is easy to write short by one entry, and catching that locally is faster than discovering it in the cross-compile step.

An architecture mismatch surfaces only at invocation time as an Runtime.InvalidEntrypoint/exec-format error, not at build or package time — the same failure class as the Python wheel mismatch below.

A typical Go Worker zip lands around 10–15 MB, well under the 50 MB direct-upload limit.

Python

Install dependencies into a local directory for packaging, using --platform for Linux-compatible binaries: <!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:206-207 -->

pip install --target ./package --platform manylinux2014_x86_64 --only-binary=:all: temporalio

<!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:210 -->

Pin the download to the Lambda runtime's Python version and architecture, not your local interpreter's. If they differ (e.g. local 3.14 vs the function's python3.13), add --python-version 3.13 alongside --only-binary=:all: so pip fetches runtime-matching wheels, and keep --platform (manylinux2014_x86_64 for x86_64, manylinux2014_aarch64 for arm64) consistent with the function's --architectures. Mismatches surface as import errors only at invocation time, not at package time.

To include OpenTelemetry support, install temporalio[lambda-worker-otel] instead. <!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:213-214 -->

Package dependencies and application code: <!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:216 -->

cd package && zip -r ../function.zip . && cd ..
zip function.zip lambda_function.py my_workflows.py my_activities.py

<!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:218-220 -->

TypeScript

Build the Workflow bundle and compile the project: <!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:226 -->

npx ts-node src/scripts/build-workflow-bundle.ts
npx tsc

<!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:229-230 -->

Install production dependencies and package everything: <!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:233 -->

npm install --omit=dev
zip -r function.zip lib/ node_modules/ workflow-bundle.js

<!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:236-237 -->

Deploy the Lambda function

A freshly created execution role may not be assumable immediately. create-function can fail with an assume-role / "cannot be assumed by Lambda" error because of IAM propagation delay. Wait a few seconds and retry; it is not a policy error, so do not start rewriting the trust policy.

Operator CLI config does not reach the function. All three CLI mechanisms above — exported TEMPORAL_* variables, --profile, and --env — configure the temporal CLI on the operator's machine only. The function reads its own environment, set by the --environment block below (or a secret store). A user with a working --env prod or --profile prod still needs every value written into that block; nothing is inherited. Treat their CLI configuration as the source of the values, not a substitute for setting them.

Resolve the values before building the block, and check they are not empty. The heredoc below expands shell variables, which hold values only under the env-var mechanism. Under --env or --profile they are unset, and an unset variable expands to an empty string: the JSON stays valid, create-function succeeds, and the function deploys with "TEMPORAL_ADDRESS":"" — failing at first invocation with a connection error that looks nothing like its cause. Populate them from whichever mechanism the user actually has (temporal env get --env prod, temporal config get --prop address), then guard:

: "${TEMPORAL_ADDRESS:?resolve this before deploying}"
: "${TEMPORAL_NAMESPACE:?resolve this before deploying}"
: "${TEMPORAL_API_KEY:?resolve this before deploying}"

Pass the API key through a file or shell variable, not inline in the shell history. Building the --environment block inline puts the API key into shell history and into any command echo. Reference it from the environment and pass the block via a file instead:

cat > /tmp/lambda-env.json <<EOF
{"Variables":{"HOME":"/tmp",
  "TEMPORAL_ADDRESS":"${TEMPORAL_ADDRESS}",
  "TEMPORAL_NAMESPACE":"${TEMPORAL_NAMESPACE}",
  "TEMPORAL_API_KEY":"${TEMPORAL_API_KEY}"}}
EOF
aws lambda create-function ... --environment file:///tmp/lambda-env.json
rm /tmp/lambda-env.json

This is still a plaintext env var on the function — acceptable for a development walkthrough only, and only if you say so explicitly. See "Environment variables" below for the production pattern.

Go
aws lambda create-function \
  --function-name my-temporal-worker \
  --runtime provided.al2023 \
  --handler bootstrap \
  --role <EXECUTION_ROLE_ARN> \
  --zip-file fileb://function.zip \
  --timeout 600 \
  --memory-size 256 \
  --environment '{"Variables":{"HOME":"/tmp","TEMPORAL_ADDRESS":"<your-temporal-address>:7233","TEMPORAL_NAMESPACE":"<your-namespace>","TEMPORAL_API_KEY":"<your-api-key>"}}'

<!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:250-260 -->

  • --runtime: provided.al2023 for custom Go binaries. <!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:265 -->

  • --handler: bootstrap when using the provided.al2023 custom runtime. <!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:266 -->

Python
aws lambda create-function \
  --function-name my-temporal-worker \
  --runtime python3.13 \
  --handler lambda_function.lambda_handler \
  --role <EXECUTION_ROLE_ARN> \
  --zip-file fileb://function.zip \
  --timeout 600 \
  --memory-size 256 \
  --environment '{"Variables":{"TEMPORAL_ADDRESS":"<your-temporal-address>:7233","TEMPORAL_NAMESPACE":"<your-namespace>","TEMPORAL_API_KEY":"<your-api-key>"}}'

<!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:271-281 -->

  • --runtime: python3.13 (or another supported Python version). <!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:286 -->

  • --handler: lambda_function.lambda_handler (entry point in module.function format, must point to the handler returned by run_worker). <!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:287 -->

TypeScript
aws lambda create-function \
  --function-name my-temporal-worker \
  --runtime nodejs22.x \
  --handler lib/index.handler \
  --role <EXECUTION_ROLE_ARN> \
  --zip-file fileb://function.zip \
  --timeout 600 \
  --memory-size 256 \
  --environment '{"Variables":{"HOME":"/tmp","TEMPORAL_ADDRESS":"<your-temporal-address>:7233","TEMPORAL_NAMESPACE":"<your-namespace>","TEMPORAL_API_KEY":"<your-api-key>"}}'

<!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:292-302 -->

  • --runtime: nodejs22.x (or another supported Node.js version, 20+). <!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:307 -->

  • --handler: lib/index.handler (entry point in module.export format, must point to the handler exported by runWorker). <!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:308 -->

Wait for the function to become Active

create-function returns immediately with "State": "Pending". The function cannot be invoked and publish-version fails while it is pending, so block on the state transition before the next step rather than sleeping a guessed interval:

aws lambda wait function-active-v2 --function-name my-temporal-worker

Use aws lambda wait function-updated-v2 after update-function-code for the same reason (see versioning.md). The -v2 suffix is the AWS CLI v2 waiter name; on AWS CLI v1 the waiters are function-active and function-updated. If neither resolves, poll instead:

aws lambda get-function --function-name my-temporal-worker \
  --query 'Configuration.[State,LastUpdateStatus]' --output text
Common parameters (all SDKs)

<!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:315-320 -->

ParameterDescription
--roleARN of the Lambda execution role, which grants the function permission to run (trusted principal: lambda.amazonaws.com). This is separate from the role Temporal uses to invoke the function. The role must have at least the AWSLambdaBasicExecutionRole managed policy attached. (See iam.md for the execution role.)
--zip-filePath to your packaged deployment zip.
--timeoutInvocation deadline in seconds. Maximum time each Lambda invocation can run before AWS terminates it. Set high enough for the Worker to start, process Tasks, and shut down gracefully.
--memory-sizeMemory in MB allocated to each invocation.

Caution: AWS Lambda functions default to a 3-second timeout, which is too short for the Worker to start, connect to Temporal, and register the Task Queue. If the first invocation times out before the Worker polls, the Task Queue binding is never created and the Lambda is never invoked again. Always set --timeout high enough for the Worker to start, process Tasks, and shut down gracefully. <!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:328-337 -->

Environment variables

<!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:321-326 -->

VariableDescription
HOMESet to /tmp in the Go and TypeScript examples above. Lambda's filesystem is read-only outside /tmp, so anything the runtime or config loader resolves relative to the home directory needs a writable target. The docs omit it from the Python example; including it there is harmless.
TEMPORAL_ADDRESSTemporal frontend address (e.g., <namespace>.<account>.tmprl.cloud:7233).
TEMPORAL_NAMESPACETemporal Namespace. For Temporal Cloud, the fully-qualified <namespace_id>.<account_id>, not the bare name.
TEMPORAL_TASK_QUEUETask Queue name. Overrides the value set in code.
TEMPORAL_TLS_CLIENT_CERT_PATHPath to the TLS client certificate file for mTLS authentication.
TEMPORAL_TLS_CLIENT_KEY_PATHPath to the TLS client key file for mTLS authentication.
TEMPORAL_API_KEYAPI key for API key authentication. Supplying it auto-enables TLS; mTLS cert paths are not needed.

The serverless Worker packages read environment variables and configuration files automatically at startup. For the full list of supported environment variables, config file format, and profiles, see the Environment configuration docs (/develop/environment-configuration). <!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:339-341 -->

Sensitive values like TLS keys and API keys should be encrypted at rest. <!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:343-344 -->

The --environment examples above pass TEMPORAL_API_KEY inline for brevity — that is acceptable for development only. For production, store the API key (or TLS private key) in AWS Secrets Manager or SSM Parameter Store, grant the execution role secretsmanager:GetSecretValue (or ssm:GetParameter), and load it at cold start before the Worker initializes — for example, at module scope in the handler file, fetch the secret and set os.environ["TEMPORAL_API_KEY"] so the serverless Worker package reads it at startup. Do not commit key values into the --environment block for production functions.

For updating the function code and publishing immutable versions, see versioning.md.

Step 3: Configure IAM for Temporal invocation

Step 3 (execution role, Temporal invocation role, and CloudFormation for Temporal Cloud and self-hosted) lives in iam.md. Complete it before Step 4.

Step 4: Create Worker Deployment Version

Create a Worker Deployment Version with a compute provider that points to your Lambda function. The compute configuration tells Temporal how to invoke your Worker: the provider type (aws-lambda), the Lambda function ARN, and the IAM role to assume. The deployment name and build ID must match the values in your Worker code. <!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:529-532 -->

Using Temporal UI

<!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:539-553 -->

  1. In the Temporal UI, open your Namespace.

  2. In the left pane, select Workers.

  3. Click Create Worker Deployment in the upper right corner.

  4. Under Configuration, enter a Name and Build ID (must match DeploymentName and BuildID in your Worker code).

  5. Under Compute, select AWS Lambda and provide:

    • Lambda ARN: the ARN of your Lambda function.

    • IAM Role ARN: the ARN of the role Temporal assumes to invoke your Lambda function (the RoleARN output from the CloudFormation stack). This is not the Lambda execution role or your own IAM user/role.

    • External ID: the same value passed to the CloudFormation template.

  6. Click Save.

When you create a version through the UI, the version is automatically set as current. <!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:552 -->

Using Temporal CLI

Use the CLI for manual setup, shell scripts, and CI/CD pipelines. When you create a version through the CLI, you must set the version as current as a separate step. <!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:558-559 -->

Check the CLI version before relying on these commands. The worker deployment create-version subcommand and its --aws-lambda-* flags only exist in recent Temporal CLI builds, and a CLI old enough to lack them fails in a way that looks like a syntax mistake:

temporal --version
temporal worker deployment create-version --help

If the subcommand or the flags are missing, upgrade. Observed bounds: v1.5.0 (Homebrew) lacked create-version entirely; v1.8.0 (standalone) had the serverless flags. The exact minimum version is unconfirmed against the CLI changelog, so treat those as bounds rather than a threshold. A current standalone build can be installed alongside a package-managed one without disturbing it — worth doing rather than upgrading a CLI the user may depend on elsewhere.

First, create the Worker Deployment if it does not already exist: <!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:561 -->

temporal worker deployment create \
  --namespace <YOUR_NAMESPACE> \
  --name my-app

<!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:563-567 -->

Then create the version with the compute provider configuration: <!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:569 -->

temporal worker deployment create-version \
  --namespace <YOUR_NAMESPACE> \
  --deployment-name my-app \
  --build-id build-1 \
  --aws-lambda-function-arn <LAMBDA_FUNCTION_ARN> \
  --aws-lambda-assume-role-arn <INVOCATION_ROLE_ARN> \
  --aws-lambda-assume-role-external-id <EXTERNAL_ID>

<!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:571-579 -->

FlagDescription
--deployment-nameWorker Deployment name. Must match DeploymentName in your Worker code.
--build-idWorker Deployment Version build ID. Must match BuildID in your Worker code.
--aws-lambda-function-arnQualified versioned ARN of the Lambda function Temporal invokes for this version (for example, function:my-worker:5). An unqualified ARN is also accepted for development.
--aws-lambda-assume-role-arnIAM role Temporal assumes to invoke the function. This is the RoleARN output from the CloudFormation stack. This is not the Lambda execution role or your own IAM user/role.
--aws-lambda-assume-role-external-idExternal ID configured in the IAM role trust policy.

<!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:581-587 -->

Validate connection

Go to Workers > Deployments > select your deployment > open the Actions menu on the version and click Validate Connection. This checks that Temporal can assume the IAM role and invoke the function. <!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:592-594 -->

Checkpoint: confirm the validation invocation actually bound the Task Queue

Do this before Step 5. Creating the version triggers one validation invocation of the Lambda. If it succeeded, the Worker connected and registered its Task Queue, and describe-version lists that Task Queue for both workflow and activity types:

temporal worker deployment describe-version \
  --deployment-name my-app --build-id build-1 --report-task-queue-stats

Task Queues listed = the invocation role, the Lambda package, the env vars, and the timeout are all working end-to-end. This is the cheapest early signal in the whole setup, and it isolates a first-invocation failure to Step 2/3 before current-version routing adds another variable. If no Task Queues are listed, stop here and go to diagnostics.md ("Failed first invocation") — setting the version current will not fix it.

Step 5: Set version as current

If you created the version through the Temporal UI, the version is already current — skip this step. <!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:598 -->

If you used the CLI, set the version as current. Without this step, tasks on the Task Queue will not route to the version, and Temporal will not invoke the Lambda function. <!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:600-601 -->

temporal worker deployment set-current-version \
  --deployment-name my-app \
  --build-id build-1 \
  --yes

<!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:603-607 -->

set-current-version asks for interactive confirmation. Without --yes (-y) it prompts, and run non-interactively (scripts, CI, or an agent shell) it exits without applying the change — which reads as a silent no-op: the command appears to succeed but the version never becomes current. Pass --yes for any non-interactive use. set-ramping-version behaves the same way. Note that delete-version does not take --yes — its gating flag is --skip-drainage. <!-- docs/cli/command-reference/worker.mdx:349-391 -->

Confirm it took effect before moving on:

temporal worker deployment describe --name my-app

Step 6: Verify deployment

Start a Workflow on the same Task Queue to confirm that Temporal invokes your Lambda Worker. <!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:611 -->

temporal workflow start \
  --task-queue my-task-queue \
  --type MyWorkflow \
  --input '"Hello, serverless!"'

<!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:613-618 -->

Verify the invocation by checking: <!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:623 -->

  • Temporal UI: The Workflow execution should show task completions in the event history. <!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:625 -->

  • AWS CloudWatch Logs: The Lambda function's log group (/aws/lambda/my-temporal-worker) should show invocation logs with the Worker startup, task processing, and graceful shutdown. Requires the execution role to have CloudWatch Logs permissions (included in AWSLambdaBasicExecutionRole). <!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:626-630 -->

If the Workflow does not progress or the Lambda is not invoked, see diagnostics.md. <!-- docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx:632-633 -->

Teardown

Record what you create, as you create it. These are live, billable AWS resources spread across three services plus Temporal Cloud, and their names are only knowable from the run that created them. Keep a running inventory — function name and published version numbers, execution role name, CloudFormation stack name and role name, region, deployment name and build ID — and hand it to the user at the end. Deliver the inventory before offering teardown, and do not write a teardown script until they ask for one. Reconstructing the inventory later means scanning the account, which the skill otherwise tells you not to do.

To remove a serverless Worker deployment (for example, after an evaluation), tear down in this order so nothing is left invoking or being invoked.

The Lambda function must go before the Worker Deployment Version, not after. The intuitive order — Temporal first, so nothing is left invoking — deadlocks, because the version refuses to delete while pollers are active and the pollers are the still-running Lambda. Deleting the function is what drains them. Expect the Temporal side to be split across the sequence for this reason.

  1. Unset the current version. A Current version cannot be deleted, and a Worker Deployment with versions cannot be deleted either — so this deadlock has to be broken first. --skip-drainage does not help here; it waives the draining check, not the Current restriction.

    temporal worker deployment set-current-version \
      --deployment-name my-app --unversioned --yes

    If other versions exist and should keep serving, set one of them current instead of --unversioned.

  2. Delete the Lambda function. This removes all published versions, and ends the invocation that is still polling:

    aws lambda delete-function --function-name my-temporal-worker
  3. Wait for the version to drain, then delete it. Poller registration is server-side and expires on a TTL after the Worker stops, so delete-version can still fail with active pollers for a while after the function is gone — and the poller list can read empty before drainage has actually completed. Poll for DrainageStatus: drained rather than retrying blind:

    temporal worker deployment describe-version \
      --deployment-name my-app --build-id build-1
    temporal worker deployment delete-version \
      --deployment-name my-app --build-id build-1
  4. Delete the Worker Deployment itself, once it has no versions left. This stops its WCI (one WCI runs per version with a compute provider); confirm the WCI has moved to Completed, and that any other deployment's WCI is still Running.

    temporal worker deployment delete --name my-app

    The deployment may still appear in list output immediately afterward — index lag, not a failed delete. Confirm with describe.

  5. Delete the CloudFormation stack that created the Temporal invocation role — only if this deployment created it. One invocation role can authorize several Worker Lambdas, so a pre-existing stack may still be in use by another deployment; in that case remove just this function's ARN from its LambdaFunctionARNs instead (see iam.md).

    aws cloudformation delete-stack --stack-name <STACK_NAME> --region <AWS_REGION>
  6. If you created a dedicated execution role, delete it — detach its managed policies first, or delete-role fails with DeleteConflict: Cannot delete entity, must detach all policies first:

    ROLE=<EXECUTION_ROLE_NAME>
    aws iam list-attached-role-policies --role-name "$ROLE" \
      --query 'AttachedPolicies[].PolicyArn' --output text \
      | tr '\t' '\n' | while read -r P; do aws iam detach-role-policy --role-name "$ROLE" --policy-arn "$P"; done
    aws iam delete-role --role-name "$ROLE"

    Skip this if the execution role predates your deployment or is shared with other functions.

  7. Delete the CloudWatch log group. delete-function does not remove it — the log group and its retained events survive the function and keep accruing storage charges:

    aws logs delete-log-group --log-group-name /aws/lambda/my-temporal-worker
  8. Ask whether to revoke the Temporal Cloud API key — do not revoke it as a matter of course. The key is account-scoped, not deployment-scoped: tearing this deployment down does not mean the user is finished with Temporal Cloud, and if the key was created during this run (see skill-temporal-ops) it is the one they now need for their next deploy, for tcld, and for every other Worker in the Namespace. Keep it unless they say otherwise. On a yes, revoke it last — it is the credential authenticating every Temporal command above it. Like set-current-version, tcld apikey delete prompts for confirmation and, run non-interactively, exits without deleting anything; the exit code looks clean while the key is still live. Pass --auto_confirm and confirm from the list output that the key is gone rather than trusting the exit code.

    tcld apikey delete --id <KEY_ID> --auto_confirm
    tcld apikey list

Requirements and compatibility

Written for: OpenAI Codex, Claude Code

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Needs: Claude Code frontmatter, Needs an API key

Security

What our scanner noticed· Low risk

  • Instructions that use the network

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Created by
Temporal
Publisher
OpenAI (Official)
Distributed by
OpenAI
Source
openai/plugins · plugins/temporal/skills/temporal-serverless · commit d420d13
Licence
MIT License (redistribution allowed)
  • LICENSE (added by AutoGineer from plugins/temporal/LICENSE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by Temporal or OpenAI.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download temporal-serverless.zip