Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
323f016
Document Serverless Workers on AgentCore Runtime
lennessyy Sep 9, 2026
c5f26a7
Clarify AgentCore Runtime session lifecycle
lennessyy Sep 9, 2026
e8933a8
Explain AgentCore Worker idle shutdown
lennessyy Sep 9, 2026
ae4df10
Lead AgentCore lifecycle with Worker shutdown policy
lennessyy Sep 9, 2026
159c66f
Clarify AgentCore idle policy guidance
lennessyy Sep 9, 2026
8ab5cf9
Document AgentCore Worker shutdown policy
lennessyy Sep 9, 2026
573b08d
Share Serverless Worker autoscaling guidance
lennessyy Sep 9, 2026
0c051e2
Add Python AgentCore Serverless Worker guide
lennessyy Sep 10, 2026
3a4455c
Add AgentCore Worker idle policy example
lennessyy Sep 10, 2026
13fcfd4
Use periods in AgentCore prose
lennessyy Sep 10, 2026
0817f16
Make AgentCore idle policy self-contained
lennessyy Sep 10, 2026
9f1d16e
Give an AgentCore Worker idle signal example
lennessyy Sep 10, 2026
702f801
Remove unsupported Workflow Task idle guidance
lennessyy Sep 10, 2026
b0e0e5e
Add fixed lease idle policy example
lennessyy Sep 10, 2026
a47de75
Remove unsupported AgentCore lease guidance
lennessyy Sep 10, 2026
ad43d78
Clarify Python Worker idle detection
lennessyy Sep 10, 2026
9f07493
Explain AgentCore sample ActivityTracker
lennessyy Sep 10, 2026
60d919a
Explain AgentCore Worker retirement policies
lennessyy Sep 10, 2026
162b237
Explain ActivityTracker idle behavior
lennessyy Sep 10, 2026
a3752a6
State exact AgentCore idle condition
lennessyy Sep 10, 2026
b5d85dc
Focus AgentCore Worker lifecycle examples
lennessyy Sep 10, 2026
167adcb
Sync AgentCore Worker examples from sample
lennessyy Sep 10, 2026
ce63708
Tighten AgentCore idle policy context
lennessyy Sep 10, 2026
b5c80b8
Lead AgentCore lifecycle guidance with impact
lennessyy Sep 10, 2026
be60a1e
Add AgentCore Serverless Worker deployment guide
lennessyy Sep 10, 2026
cb56f06
Document durable agents on AgentCore
lennessyy Sep 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
250 changes: 250 additions & 0 deletions docs/develop/python/workers/serverless-workers/agentcore.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
---
id: agentcore
title: Serverless Workers on Amazon Bedrock AgentCore Runtime - Python SDK
sidebar_label: Amazon Bedrock AgentCore
description: Run a Temporal Worker on Amazon Bedrock AgentCore Runtime using the Python SDK.
slug: /develop/python/workers/serverless-workers/agentcore
toc_max_heading_level: 4
tags:
- Workers
- Python SDK
- Serverless
- Amazon Bedrock AgentCore
---

import { ReleaseNoteHeader } from '@site/src/components'

<ReleaseNoteHeader type="prerelease">
Amazon Bedrock AgentCore Runtime support is in Pre-release, and its APIs may change in backwards-incompatible ways.
</ReleaseNoteHeader>

On Amazon Bedrock AgentCore Runtime, you run a standard long-lived Python Worker inside an AgentCore Runtime handler.
Temporal starts the handler when the Worker Controller Instance needs capacity. The handler starts a Worker that polls
the Task Queue, then stops it when your idle policy decides to release capacity.

The Worker uses the normal Python SDK. The handler uses the `bedrock-agentcore` package to receive AgentCore Runtime
invocations.

For the provider behavior, including autoscaling, Worker Versioning, and the Runtime session lifecycle, see
[Serverless Workers on Amazon Bedrock AgentCore Runtime](/serverless-workers/agentcore).
For the infrastructure procedure, see
[Deploy a Serverless Worker on Amazon Bedrock AgentCore Runtime](/production-deployment/worker-deployments/serverless-workers/agentcore).

## Install the AgentCore Runtime SDK {/* #install-agentcore-runtime-sdk */}

Install the AgentCore Runtime SDK alongside the Temporal Python SDK:

```bash
pip install bedrock-agentcore
```

## Create a versioned Worker {/* #versioned-worker */}

Serverless Workers require [Worker Versioning](/worker-versioning). Create the Worker as you would any long-lived
Python Worker, then set `deployment_config` to declare its Worker Deployment Version and enable versioning:

```python
worker = Worker(
# ...
deployment_config=WorkerDeploymentConfig(
version=WorkerDeploymentVersion(
deployment_name=DEPLOYMENT_NAME,
build_id=BUILD_ID,
),
use_worker_versioning=True,
default_versioning_behavior=VersioningBehavior.PINNED,
),
)
```

`TEMPORAL_DEPLOYMENT_NAME` and `TEMPORAL_BUILD_ID` must match the Worker Deployment Version that you create with
`temporal worker deployment create-version`. Configure that Worker Deployment Version with the AgentCore Runtime
endpoint that Temporal invokes. For the endpoint configuration, see
[Worker Versioning](/serverless-workers/agentcore#worker-versioning).

Every Workflow needs a [versioning behavior](/worker-versioning#versioning-behaviors), either `PINNED` or
`AUTO_UPGRADE`. Setting `default_versioning_behavior` as shown applies `PINNED` behavior to every Workflow on the
Worker. To set the behavior per Workflow instead, pass `versioning_behavior` to the `@workflow.defn` decorator.

## Start the Worker from the Runtime handler {/* #runtime-handler */}

AgentCore Runtime invokes an HTTP handler. Use `BedrockAgentCoreApp` to provide that handler, and use `async_task` so
AgentCore keeps the Runtime active while the Worker polls:

<!--SNIPSTART python-agentcore-runtime-handler-->
[bedrock_agentcore/strands-agent/agentcore_worker.py](https://github.com/temporalio/samples-python/blob/5b0fe65efe934388d35eda430fb11df6899ce1d3/bedrock_agentcore/strands-agent/agentcore_worker.py)
```py
@app.entrypoint
@app.async_task # keeps /ping on "HealthyBusy" until this returns
async def invoke(payload: dict) -> dict:
"""Poll until idle, then drain. The payload is unused: every call is a new session and new worker."""
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()],
)

task_queue = os.environ.get("TEMPORAL_TASK_QUEUE", workflows.TASK_QUEUE)
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)
return {"message": "worker drained", "task_queue": task_queue}
```
<!--SNIPEND-->

The payload does not represent a Workflow input. The Worker Controller Instance invokes the endpoint to add Worker
capacity. Applications start Workflows through the Temporal Client, as usual.

## Configure the Temporal connection {/* #configure-connection */}

The `temporalio.envconfig` package loads [Temporal Client](/develop/python/client/temporal-client) configuration from
environment variables and an optional TOML configuration file. Set the Temporal address, Namespace, Task Queue, and
Worker Deployment Version values as Runtime environment variables. Store a Temporal Cloud API key or TLS material in a
secret store rather than in the Runtime definition.

For the supported connection variables, config-file format, and profiles, see
[Environment configuration](/develop/environment-configuration).

## Stop and drain the Worker {/* #stop-and-drain-the-worker */}

AgentCore cannot tell when a Worker that is still polling has no Temporal work. The Runtime remains busy while the
`async_task` handler runs, so it can remain active until its eight-hour maximum lifetime. To release capacity sooner,
have the handler detect when the Worker has no useful work and return.

When the condition remains true for an idle period, leave the `async with worker` block. The Worker stops polling for
new Tasks and gives in-flight Activities time to complete before the Runtime handler returns.

The following example from the
[AgentCore sample Worker](https://github.com/temporalio/samples-python/blob/5b0fe65efe934388d35eda430fb11df6899ce1d3/bedrock_agentcore/strands-agent/agentcore_worker.py)
defines an `ActivityTracker`. It uses an [Activity inbound Interceptor](/develop/python/workers/interceptors) to count
running Activities.

<!--SNIPSTART python-agentcore-activity-tracker-->
[bedrock_agentcore/strands-agent/agentcore_worker.py](https://github.com/temporalio/samples-python/blob/5b0fe65efe934388d35eda430fb11df6899ce1d3/bedrock_agentcore/strands-agent/agentcore_worker.py)
```py
# How long the Worker keeps polling after it goes idle.
DEBOUNCE = float(os.environ.get("AGENTCORE_DEBOUNCE_SECONDS", "60"))
# How long the drain waits for in-flight Activities (a model or tool call).
DRAIN = timedelta(seconds=120)


class ActivityTracker(Interceptor):
"""Tracks in-flight activities and blocks until AGENTCORE_DEBOUNCE_SECONDS elapses with no events."""

def __init__(self) -> None:
self.inflight = 0
self.changed = asyncio.Event()

def intercept_activity(
self, next: ActivityInboundInterceptor
) -> ActivityInboundInterceptor:
return _TrackedActivity(next, self)

async def wait_until_idle(self, debounce: float) -> None:
"""Return once no Activity has run for ``debounce`` seconds."""
while True:
self.changed.clear()
try:
# Wake the moment an Activity starts or finishes; a timeout
# instead means nothing has happened for the whole window.
await asyncio.wait_for(self.changed.wait(), timeout=debounce)
except asyncio.TimeoutError:
if self.inflight == 0:
return


class _TrackedActivity(ActivityInboundInterceptor):
def __init__(
self, next: ActivityInboundInterceptor, tracker: ActivityTracker
) -> None:
super().__init__(next)
self._tracker = tracker

async def execute_activity(self, input: ExecuteActivityInput):
self._tracker.inflight += 1
self._tracker.changed.set()
log.info("activity in flight: %d", self._tracker.inflight)
try:
return await self.next.execute_activity(input)
finally:
self._tracker.inflight -= 1
self._tracker.changed.set()
```
<!--SNIPEND-->

Register the tracker as a Worker Interceptor and wait for it inside the Worker context:

```python
tracker = ActivityTracker()
worker = Worker(
client,
# ...
interceptors=[tracker],
graceful_shutdown_timeout=DRAIN,
)

async with worker:
await tracker.wait_until_idle(DEBOUNCE)
```

`ActivityTracker` retires the Worker only after 60 seconds without an Activity starting or completing and with no
Activity running. A long-running Activity keeps the count above zero, so the idle policy does not interrupt it. The
two-minute `graceful_shutdown_timeout` is a safety limit for any Activity still in flight when shutdown starts.

Memory pressure can be another retirement condition. For example, the Runtime handler can monitor process memory and
initiate the same graceful shutdown when usage crosses a threshold. Memory usage is not an idle signal. It tells you
when to recycle a Worker, not whether it has work to do. Test any memory-based policy against the Runtime's memory
limit and your Activity retry behavior.

`AGENTCORE_DEBOUNCE_SECONDS` controls the idle period. `graceful_shutdown_timeout` controls how long the Worker waits
for in-flight Activities after it stops polling. Choose both values for your workload, and account for AgentCore's
maximum Runtime lifetime. For the AgentCore lifecycle settings, see
[Lifecycle](/serverless-workers/agentcore#lifecycle).

## Keep Activities safe across Worker termination {/* #activity-recovery */}

AgentCore can end the compute that runs a Worker. An Activity running at that time can be interrupted and retried.
Use [Activity Heartbeats](/develop/python/activities/timeouts#activity-heartbeats) so a retry resumes from its last
recorded progress instead of starting over:

```python
from temporalio import activity


@activity.defn
async def my_activity(items: list[str]) -> str:
for i, item in enumerate(items):
activity.heartbeat(i)
# ... process item
return "done"
```

## Add observability {/* #add-observability */}

An AgentCore Runtime Worker emits the same traces and metrics as a Worker on other compute. For metrics export and
OpenTelemetry tracing interceptors, see [Observability - Python SDK](/develop/python/platform/observability) and the
[SDK metrics reference](/references/sdk-metrics).
9 changes: 5 additions & 4 deletions docs/develop/python/workers/serverless-workers/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ tags:
import { ReleaseNoteHeader } from '@site/src/components';

<ReleaseNoteHeader type="publicPreview">
AWS Lambda support is in Public Preview. GCP Cloud Run support is in Pre-release, and its APIs may change in
backwards-incompatible ways. To request Cloud Run access, create a [support ticket](/cloud/support#support-ticket) or
contact your account team, and [sign up for updates](https://temporal.io/pages/serverless-workers-updates) to hear
when Cloud Run reaches Public Preview.
AWS Lambda support is in Public Preview. Amazon Bedrock AgentCore Runtime and GCP Cloud Run support are in
Pre-release, and their APIs may change in backwards-incompatible ways. To request Cloud Run access, create a
[support ticket](/cloud/support#support-ticket) or contact your account team, and
[sign up for updates](https://temporal.io/pages/serverless-workers-updates) to hear when Cloud Run reaches Public Preview.
</ReleaseNoteHeader>

Serverless Workers run on ephemeral, on-demand compute rather than long-lived processes.
Expand All @@ -29,4 +29,5 @@ For the end-to-end deployment guide, see [Deploy a Serverless Worker](/productio
## Supported providers

- [**AWS Lambda**](/develop/python/workers/serverless-workers/aws-lambda) - Use the `lambda_worker` contrib package to run a Worker as a Lambda function. Covers setup, configuration, Lambda-tuned defaults, and observability.
- [**Amazon Bedrock AgentCore Runtime**](/develop/python/workers/serverless-workers/agentcore) - Run a standard Worker from an AgentCore Runtime handler. Covers the handler, Worker Versioning, connection configuration, and Worker shutdown.
- [**GCP Cloud Run**](/develop/python/workers/serverless-workers/cloud-run) - Run a standard Worker on a Cloud Run worker pool. Covers the versioned Worker setup, connection configuration, and handling scale-in.
36 changes: 24 additions & 12 deletions docs/encyclopedia/workers/serverless-workers/aws-lambda.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -24,26 +24,38 @@ For a step-by-step deployment guide, see [Deploy a Serverless Worker on AWS Lamb

## Autoscaling {/* #autoscaling */}

The Lambda autoscaling algorithm is event-driven and reactive.
The autoscaling algorithm in this section applies to Serverless Workers on AWS Lambda and Amazon Bedrock AgentCore
Runtime. The compute providers have different Worker lifecycles after a scale-out action. For AgentCore Runtime
lifecycle details, see [Serverless Workers on Amazon Bedrock AgentCore Runtime](/serverless-workers/agentcore#lifecycle).

The autoscaling algorithm is event-driven and reactive.
Sync match failure is the primary control signal, and backlog aids sizing.

When the [WCI](/serverless-workers#worker-controller-instance) needs more capacity, it calls the Lambda `InvokeFunction` API to start new Workers.
Each call is a discrete action ("invoke N more functions"), not a target state.
Temporal calls that API from outside your network, so no inbound connection to the function is needed.
The WCI does not manage a fleet of instances.
When the [WCI](/serverless-workers#worker-controller-instance) needs more capacity, it invokes the compute provider to
start new Workers: the Lambda `InvokeFunction` API for Lambda or an AgentCore Runtime endpoint for AgentCore Runtime.
Each call is a discrete action ("start N more Workers"), not a target state. Temporal calls the provider API from
outside your network, so no inbound connection to the Worker is needed. The WCI does not manage a fleet of instances.

### Scale-out {/* #scale-out */}

On sync match failure, the WCI invokes new Lambda functions.
Because Lambda cold start is sub-second to low single-digit seconds, reactive-only control does not create meaningful backlog overshoot.
The WCI can scale from zero with low latency.
On sync match failure, the WCI starts new Workers through the compute provider API.

For Lambda, cold start is sub-second to low single-digit seconds, so reactive-only control does not create meaningful
backlog overshoot. The WCI can scale from zero with low latency.

For AgentCore Runtime startup and session behavior, see
[Lifecycle](/serverless-workers/agentcore#lifecycle).

### Scale-in {/* #scale-in */}

Scale-in is automatic.
Each Lambda invocation runs until the Worker has finished processing available Tasks or approaches the 15-minute execution time limit, then shuts down.
There is no drain logic or stabilization window.
The WCI does not need to actively remove capacity.
The WCI does not maintain a target number of Workers or actively remove capacity. The provider and Worker lifecycle
determine when a Worker stops.

On Lambda, each invocation runs until the Worker has finished processing available Tasks or approaches the 15-minute
execution time limit, then shuts down. There is no drain logic or stabilization window.

On AgentCore Runtime, Worker shutdown and AgentCore session lifecycle settings determine when a Worker stops. See
[Lifecycle](/serverless-workers/agentcore#lifecycle).

### Instance model {/* #instance-model */}

Expand Down
Loading