# Deploy a Serverless Worker on Amazon Bedrock AgentCore Runtime

> For the complete documentation index, see [llms.txt](https://docs.temporal.io/llms.txt).
> Any documentation page is available as raw Markdown by appending `.md` to its URL.

> Add a Python Worker to an existing AgentCore application and configure Temporal to start capacity when Task Queue demand increases.

> **Pre-release**
> Amazon Bedrock AgentCore Runtime support is in Pre-release, and its APIs may change in backwards-incompatible ways.

This page shows how to add a Python [Serverless Worker](/serverless-workers) to an existing Amazon Bedrock AgentCore
application, deploy it to AgentCore Runtime, and connect it to a Worker Deployment Version. It assumes that your
Workflow and Activity code and AgentCore project are already in place.

For a tutorial that walks you through setting up an AgentCore project from scratch, see [Build a durable agent on
Amazon Bedrock AgentCore](/guides/durable-agent-on-agentcore). That guide starts with the [Python Strands AgentCore
sample](https://github.com/temporalio/samples-python/tree/schoeff/strands-agent/bedrock_agentcore/strands_agent) and explains
the agent architecture, Workflow and Activity boundaries, AgentCore project configuration, and deployment from start to
finish. Use this page when you only need the Worker deployment procedure.

For details about the Worker implementation and lifecycle, see [Serverless Workers on Amazon Bedrock AgentCore Runtime -
Python SDK](/develop/python/workers/serverless-workers/agentcore).

## Prerequisites 

- A Temporal Cloud account with an AWS-hosted Namespace and access to the AgentCore Serverless Workers Pre-release. For
  a self-hosted Temporal Service v1.32.0 or later, complete the
  [self-hosted setup](/production-deployment/worker-deployments/serverless-workers/agentcore/self-hosted-setup) first.
- [Temporal CLI v1.8.3](https://github.com/temporalio/cli/releases/tag/v1.8.3) or later, configured for your Namespace.
- An existing AgentCore project with `agentcore/agentcore.json`, `agentcore/aws-targets.json`, and a generated AgentCore
  CDK project.
- An AWS account in an [AgentCore-supported Region](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/agentcore-regions.html),
  with the [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) configured for that
  account and permission to create AgentCore resources, CloudFormation stacks, and IAM roles. See
  [IAM permissions for AgentCore Runtime](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-permissions.html).
- Node.js 20 or later, the [AgentCore CLI](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/agentcore-get-started-cli.html),
  and the [AWS CDK](https://docs.aws.amazon.com/cdk/v2/guide/getting-started.html) installed. Bootstrap the CDK in the
  target account and Region.

## 1. Configure the Worker Runtime 

`agentcore/agentcore.json` is the [AgentCore CLI project
configuration](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/agentcore-get-started-cli.html). Its
`runtimes` array defines the AgentCore Runtime resources that the CLI deploys.

The `entrypoint` field names the Python file that AgentCore starts. That file must implement the [AgentCore Runtime HTTP
protocol contract](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-http-protocol-contract.html) by
serving the Runtime's `/invocations` and `/ping` endpoints. For a Serverless Worker, `/invocations` starts the Temporal
Worker and acknowledges the request. For an implementation example, see the [Python Runtime entry
point](/develop/python/workers/serverless-workers/agentcore#runtime-handler).

The following fragment from the Python Strands AgentCore sample configures that entry point, a public network, and the
named endpoint that Temporal invokes:

```json
{
  "name": "temporal_strands_worker",
  "build": "CodeZip",
  "entrypoint": "agentcore_worker.py",
  "codeLocation": ".",
  "runtimeVersion": "PYTHON_3_12",
  "networkMode": "PUBLIC",
  "protocol": "HTTP",
  "authorizerType": "AWS_IAM",
  "endpoints": {
    "temporal": {
      "version": 1,
      "description": "Invoked by Temporal Cloud Serverless Workers"
    }
  }
}
```

In this initial configuration, `version: 1` selects the first AgentCore Runtime version. Verify the named endpoint's
version after deployment in [Step 3](#deploy-runtime).

Within the same Runtime object, add the Temporal connection, Task Queue, Worker Deployment name, and Build ID to the
`envVars` array:

```json
{
  "envVars": [
    {
      "name": "TEMPORAL_ADDRESS",
      "value": "<NAMESPACE>.<ACCOUNT>.tmprl.cloud:7233"
    },
    {
      "name": "TEMPORAL_NAMESPACE",
      "value": "<NAMESPACE>.<ACCOUNT>"
    },
    {
      "name": "TEMPORAL_API_KEY",
      "value": "<TEMPORAL_API_KEY>"
    },
    {
      "name": "TEMPORAL_TASK_QUEUE",
      "value": "<TASK_QUEUE>"
    },
    {
      "name": "TEMPORAL_DEPLOYMENT_NAME",
      "value": "<DEPLOYMENT_NAME>"
    },
    {
      "name": "TEMPORAL_BUILD_ID",
      "value": "<BUILD_ID>"
    }
  ]
}
```

Replace `<NAMESPACE>.<ACCOUNT>` with your Temporal Cloud Namespace ID, `<TEMPORAL_API_KEY>` with its API key, and
`<TASK_QUEUE>` with the Task Queue used by your application. Choose `<DEPLOYMENT_NAME>` and `<BUILD_ID>` for this Worker
version.

Do not commit a populated Temporal Cloud API key. For a production deployment, store it in AWS Secrets Manager, grant
the Runtime execution role permission to read it, and load it in the Runtime entry point. The Runtime execution role is
separate from the invocation role that Temporal assumes.

## 2. Add the Worker code 

For a `CodeZip` Runtime, AgentCore packages the directory identified by `codeLocation`. The `entrypoint` path is
relative to that directory. The directory must contain the entry point, every local module that it imports, and a
`pyproject.toml` file that declares the Runtime dependencies.

The sample sets `codeLocation` to `.` and uses the following layout:

```text
project-root/
├── agentcore/
│   ├── agentcore.json
│   ├── aws-targets.json
│   └── cdk/
├── agentcore_worker.py
├── workflows.py
├── activities.py
└── pyproject.toml
```

With this layout, set `entrypoint` to `agentcore_worker.py`. If your AgentCore application keeps code in a directory
such as `app/MyAgent`, set `codeLocation` to that directory and put the entry point, imported modules, and
`pyproject.toml` there.

Declare `temporalio`, `bedrock-agentcore`, and your application dependencies in `pyproject.toml`. The entry point must:

- Connect a Temporal Client and create a standard long-running Worker with your Workflows and Activities.
- Configure the Worker with the deployment name and Build ID from the Runtime environment.
- Use `BedrockAgentCoreApp` to implement the AgentCore Runtime HTTP endpoints.
- Start the Worker as an AgentCore asynchronous task and acknowledge the invocation without waiting for the Worker to
  stop.
- Stop polling and drain the Worker when its idle policy decides to release the Runtime.

The following excerpt from the Python sample implements this structure. It uses the `ActivityTracker`, `DEBOUNCE`, and
`DRAIN` values defined in the same source file to retire the Worker after an idle period. The complete linked source
file also contains the imports, creates the `BedrockAgentCoreApp`, retains the background task in `_worker`, and calls
`app.run()` when the entry point starts. For the idle-policy code and an explanation of each part, see
[Start the Worker from the Runtime handler](/develop/python/workers/serverless-workers/agentcore#runtime-handler).

<!--SNIPSTART python-agentcore-runtime-handler-->
[bedrock_agentcore/strands_agent/agentcore_worker.py](https://github.com/temporalio/samples-python/blob/schoeff/strands-agent/bedrock_agentcore/strands_agent/agentcore_worker.py)
```py
async def run_worker() -> None:
    """Poll until idle, then drain."""
    api_key = os.environ.get("TEMPORAL_API_KEY") or None
    client = await Client.connect(
        os.environ.get("TEMPORAL_ADDRESS", "localhost:7233"),
        namespace=os.environ.get("TEMPORAL_NAMESPACE", "default"),
        api_key=api_key,
        tls=bool(api_key),
        plugins=[StrandsPlugin()],
    )

    tracker = ActivityTracker()
    log.info("polling %s as %s/%s", TASK_QUEUE, DEPLOYMENT_NAME, BUILD_ID)
    # execute_code is a sync Activity, so it needs an executor to block on.
    with ThreadPoolExecutor(max_workers=4) as activity_executor:
        worker = Worker(
            client,
            task_queue=TASK_QUEUE,
            workflows=[workflows.StrandsAgentWorkflow],
            activities=[execute_code],
            activity_executor=activity_executor,
            interceptors=[tracker],
            deployment_config=WorkerDeploymentConfig(
                version=WorkerDeploymentVersion(
                    deployment_name=DEPLOYMENT_NAME, build_id=BUILD_ID
                ),
                use_worker_versioning=True,
                default_versioning_behavior=VersioningBehavior.PINNED,
            ),
            graceful_shutdown_timeout=DRAIN,
        )
        async with worker:
            await tracker.wait_until_idle(DEBOUNCE)
    log.info("worker idle for %ss; drained", DEBOUNCE)

async def _run_until_idle(task_id: int) -> None:
    """Own the Worker's whole life, and always release the async task."""
    try:
        await run_worker()
    except Exception:
        # Nothing awaits this task, so an error would otherwise be swallowed.
        log.exception("worker failed in async task")
    finally:
        # Without this the session stays HealthyBusy until MaxLifetime.
        app.complete_async_task(task_id)

@app.entrypoint
async def invoke(payload: dict) -> dict:
    """Start the Worker and acknowledge. The payload is unused."""
    # Prevent duplicate workers since we exit early
    global _worker
    if _worker is not None and not _worker.done():
        log.info("worker already polling %s", TASK_QUEUE)
        return {"message": "worker already polling", "task_queue": TASK_QUEUE}

    task_id = app.add_async_task("temporal-worker")
    _worker = asyncio.create_task(_run_until_idle(task_id))

    return {"message": "worker starting", "task_queue": TASK_QUEUE}

```
<!--SNIPEND-->

Replace `StrandsPlugin`, `StrandsAgentWorkflow`, and `execute_code` with the plugins, Workflows, and Activities used by
your application. If all Activities are asynchronous, you do not need the `ThreadPoolExecutor` or `activity_executor`.

## 3. Deploy the Worker Runtime 

From the AgentCore project directory, validate and deploy the project. Set `--target` to the `name` of the deployment
target in `agentcore/aws-targets.json`. For example, if the target is named `default`, run:

```bash
agentcore validate
agentcore deploy --target default -y
```

AgentCore packages the Worker and its dependencies, deploys the Runtime, and creates the named endpoint.

Check the deployed resources. The `--runtime` value is the Runtime object's `name` in `agentcore/agentcore.json`. The
sample Runtime is named `temporal_strands_worker`. If your Runtime has a different name, replace this value:

```bash
agentcore status --runtime temporal_strands_worker --json
agentcore status --type runtime-endpoint --json
```

Record the Runtime ARN and the ARN of the named endpoint. You use the Runtime ARN to scope the invocation role and give
the endpoint ARN to Temporal.

AgentCore creates an immutable Runtime version when you create or update a Runtime. A named endpoint remains pinned to
its configured version until you update it. For details, see [AgentCore Runtime versioning and
endpoints](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/agent-runtime-versioning.html).

Confirm that the endpoint's live version matches the Runtime version containing the Worker code and environment
configuration that you intend to deploy:

```bash
aws bedrock-agentcore-control get-agent-runtime-endpoint \
  --agent-runtime-id <RUNTIME_ID> \
  --endpoint-name <ENDPOINT_NAME> \
  --query '{status:status,liveVersion:liveVersion}' \
  --region <AWS_REGION>
```

> **⚠️ Caution:**
> Verify the endpoint version after redeploying
>
> If you redeploy the Runtime without updating its named endpoint, Temporal continues to invoke the earlier Worker code.
> Creating the Worker Deployment Version can then time out if that code does not acknowledge the invocation promptly or
> does not register the expected deployment name and Build ID.
>
> For a later Worker version, create or update a named endpoint to use the new Runtime version. Use that endpoint ARN for
> the corresponding Worker Deployment Version. Keep endpoints used by existing Worker Deployment Versions pinned to
> their original Runtime versions.
>

If you use a VPC instead of a public network, configure outbound access from the VPC to the Temporal Service. Temporal
invokes the named endpoint by assuming the IAM role that you create in [Step 4](#configure-iam).

## 4. Grant Temporal permission to invoke the Runtime 

> **ℹ️ Info:**
> Self-hosted Temporal Service
>
> If you use a self-hosted Temporal Service, create the invocation role during the
> [self-hosted setup](/production-deployment/worker-deployments/serverless-workers/agentcore/self-hosted-setup#create-invocation-role).
> Use that role when you create the Worker Deployment Version and skip the rest of this step.
>

Temporal Cloud assumes an IAM role in your AWS account to get the named endpoint and invoke the Runtime. Choose an
External ID of at least five characters. Use the same value in the role trust policy and the Worker Deployment Version.
The External ID prevents a [confused deputy](https://docs.aws.amazon.com/IAM/latest/UserGuide/confused-deputy.html)
attack.

[Download the CloudFormation template](/files/temporal-cloud-serverless-worker-agentcore-role.yaml), then deploy it.
Pass the Runtime ARN with a trailing wildcard so the policy covers the Runtime and its endpoints.

> **⚠️ Caution:**
>
> The template names the IAM role `<ROLE_NAME>-<STACK_NAME>`. An [IAM role name can contain at most 64
> characters](https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-iam-role.html). Include
> the hyphen when checking the combined length. CloudFormation cannot create the role if the combined name exceeds this
> limit.
>

```bash
aws cloudformation create-stack \
  --stack-name <STACK_NAME> \
  --template-body file://temporal-cloud-serverless-worker-agentcore-role.yaml \
  --parameters \
    ParameterKey=AssumeRoleExternalId,ParameterValue=<EXTERNAL_ID> \
    ParameterKey=AgentRuntimeARNs,ParameterValue='<AGENT_RUNTIME_ARN>*' \
    ParameterKey=RoleName,ParameterValue=<ROLE_NAME> \
  --capabilities CAPABILITY_NAMED_IAM \
  --region <AWS_REGION>
```

Wait for the CloudFormation stack to finish:

```bash
aws cloudformation wait stack-create-complete \
  --stack-name <STACK_NAME> \
  --region <AWS_REGION>
```

Then retrieve the invocation role ARN:

```bash
aws cloudformation describe-stacks \
  --stack-name <STACK_NAME> \
  --query 'Stacks[0].Outputs[?OutputKey==`RoleARN`].OutputValue' \
  --output text \
  --region <AWS_REGION>
```

The role grants `bedrock-agentcore:InvokeAgentRuntime` and `bedrock-agentcore:GetAgentRuntimeEndpoint` on the configured
Runtime resources. This role does not run the Worker code.

## 5. Create the Worker Deployment Version 

Create a [Worker Deployment Version](/production-deployment/worker-deployments/worker-versioning) whose compute
configuration points to the named AgentCore Runtime endpoint. The deployment name and Build ID must match
`TEMPORAL_DEPLOYMENT_NAME` and `TEMPORAL_BUILD_ID` in the Runtime environment from
[Step 1](#configure-worker-runtime).

**Temporal Cloud UI**

In the Temporal Cloud UI, open your Namespace and select **Workers** > **Create Worker Deployment**. Provide these
values:

- **Name**: the value of `TEMPORAL_DEPLOYMENT_NAME` in the Runtime environment.
- **Build ID**: the value of `TEMPORAL_BUILD_ID` in the Runtime environment.
- **Compute Provider**: select **Amazon Bedrock AgentCore Runtime**.
- **Runtime endpoint ARN**: the named endpoint ARN from [Step 3](#deploy-runtime).
- **IAM role ARN**: the invocation role ARN from [Step 4](#configure-iam).
- **External ID**: the External ID from [Step 4](#configure-iam).

Save the Worker Deployment. When you create a version through the UI, the version is automatically current. Continue
to [Step 7](#verify-worker-startup).

**Temporal CLI**

Use the Temporal CLI for a self-hosted Temporal Service.

First, create the Worker Deployment if it does not already exist:

```bash
temporal worker deployment create \
  --namespace <TEMPORAL_NAMESPACE> \
  --name <DEPLOYMENT_NAME>
```

Then create the version with the AgentCore compute configuration:

```bash
temporal worker deployment create-version \
  --namespace <TEMPORAL_NAMESPACE> \
  --deployment-name <DEPLOYMENT_NAME> \
  --build-id <BUILD_ID> \
  --aws-agentcore-endpoint-arn <RUNTIME_ENDPOINT_ARN> \
  --aws-agentcore-assume-role-arn <INVOCATION_ROLE_ARN> \
  --aws-agentcore-assume-role-external-id <EXTERNAL_ID>
```

For Temporal Cloud, check whether Temporal can reach the endpoint by opening the Worker Deployment Version in the
Temporal Cloud UI and selecting **Actions** > **Validate Connection**. This checks that Temporal can assume the
invocation role, get the named endpoint, and invoke the Runtime.

## 6. Set the version as current 

If you used the Temporal CLI, set the version as current:

```bash
temporal worker deployment set-current-version \
  --namespace <TEMPORAL_NAMESPACE> \
  --deployment-name <DEPLOYMENT_NAME> \
  --build-id <BUILD_ID>
```

This command asks you to confirm because it changes which version receives new Tasks. Pass `--yes` to skip the prompt.
If you created the version in the Temporal Cloud UI, it is already current.

## 7. Verify Worker startup 

Submit work to the configured Task Queue using your application. When no Worker is polling, Temporal invokes the named
AgentCore Runtime endpoint. The Runtime starts the Worker, and the Worker polls and processes Tasks.

You can confirm the deployment in these places:

- **Temporal UI**: Open the Worker Deployment Version and confirm that a Worker has polled the Task Queue. In Temporal
  Cloud, also confirm that the connection is valid.
- **AgentCore logs**: Run `agentcore logs --runtime <RUNTIME_NAME>` to see the Worker start and process Tasks.
- **Temporal CLI**: Run `temporal worker deployment describe --name <DEPLOYMENT_NAME>` to inspect the deployment and
  current version.
