diff --git a/docs/develop/python/workers/serverless-workers/agentcore.mdx b/docs/develop/python/workers/serverless-workers/agentcore.mdx new file mode 100644 index 0000000000..48d533931b --- /dev/null +++ b/docs/develop/python/workers/serverless-workers/agentcore.mdx @@ -0,0 +1,280 @@ +--- +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' + + + Amazon Bedrock AgentCore Runtime support is in Pre-release, and its APIs may change in backwards-incompatible ways. + + +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. Register the Worker as an +asynchronous AgentCore task, then return an acknowledgment while the Worker continues polling in the background. The +sample stores the background task in `_worker` and uses it to prevent another invocation from starting a duplicate +Worker in the same Runtime session: + + +[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} + + +``` + + +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. `add_async_task` causes AgentCore to +report the Runtime as busy while the Worker polls. `complete_async_task` releases that status after the Worker drains +or fails. + +## 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 +asynchronous task is registered, 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 complete the asynchronous task. + +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/9d5c46bed0f0f6f8a726fa91f371c4c83f232ba2/bedrock_agentcore/strands_agent/agentcore_worker.py) +defines an `ActivityTracker`. It uses an [Activity inbound Interceptor](/develop/python/workers/interceptors) to count +running Activities. + + +[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 +# 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() + + +``` + + +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). diff --git a/docs/develop/python/workers/serverless-workers/index.mdx b/docs/develop/python/workers/serverless-workers/index.mdx index 6a1299e5cc..c0d1d55f52 100644 --- a/docs/develop/python/workers/serverless-workers/index.mdx +++ b/docs/develop/python/workers/serverless-workers/index.mdx @@ -14,10 +14,10 @@ tags: import { ReleaseNoteHeader } from '@site/src/components'; - 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. Serverless Workers run on ephemeral, on-demand compute rather than long-lived processes. @@ -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. diff --git a/docs/encyclopedia/workers/serverless-workers/aws-lambda.mdx b/docs/encyclopedia/workers/serverless-workers/aws-lambda.mdx index 4547762d57..fca5bec7c3 100644 --- a/docs/encyclopedia/workers/serverless-workers/aws-lambda.mdx +++ b/docs/encyclopedia/workers/serverless-workers/aws-lambda.mdx @@ -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 */} diff --git a/docs/encyclopedia/workers/serverless-workers/index.mdx b/docs/encyclopedia/workers/serverless-workers/index.mdx index ff394ec70a..657fb5f378 100644 --- a/docs/encyclopedia/workers/serverless-workers/index.mdx +++ b/docs/encyclopedia/workers/serverless-workers/index.mdx @@ -14,10 +14,10 @@ tags: import { CaptionedImage, ReleaseNoteHeader } from '@site/src/components'; - 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. This page covers the following: @@ -39,8 +39,9 @@ in response to work on a Task Queue. A Serverless Worker uses the same Temporal SDKs as a traditional long-lived Worker, and registers Workflows and Activities the same way. What differs is that Temporal manages the Worker's lifecycle rather than you running a Worker -process. How that lifecycle works depends on the compute provider: AWS Lambda runs short-lived invocations, while GCP -Cloud Run runs a pool of long-lived instances. See [Worker lifecycle](#worker-lifecycle). +process. How that lifecycle works depends on the compute provider: AWS Lambda runs short-lived invocations, Amazon +Bedrock AgentCore Runtime runs sessions with idle and maximum-lifetime limits, and GCP Cloud Run runs a pool of +long-lived instances. See [Worker lifecycle](#worker-lifecycle). Serverless Workers require [Worker Versioning](/worker-versioning). Each Serverless Worker must be associated with a [Worker Deployment Version](/worker-versioning#deployment-versions) that has a compute provider configured. @@ -69,12 +70,13 @@ Temporal impersonates to scale it. Compute providers are only needed for Serverless Workers. Traditional long-lived Workers do not require a compute provider because the Worker process lifecycle is not managed by the Temporal server. -Temporal supports two compute providers: +Temporal supports three compute providers: -| Provider | Description | -| ------------- | ----------------------------------------------------------------------------- | -| AWS Lambda | Temporal assumes an IAM role in your AWS account to invoke a Lambda function. | -| GCP Cloud Run | Temporal scales a Cloud Run [Worker Pool](https://cloud.google.com/run/docs/resource-model#worker-pools) through the Cloud Run admin API. A Worker Pool is its own Cloud Run resource type, distinct from a Service or a Job. | +| Provider | Description | +| ------------------------------ | ----------- | +| AWS Lambda | Temporal assumes an IAM role in your AWS account to invoke a Lambda function. | +| Amazon Bedrock AgentCore Runtime | Temporal assumes an IAM role in your AWS account to invoke an AgentCore Runtime endpoint. | +| GCP Cloud Run | Temporal scales a Cloud Run [Worker Pool](https://cloud.google.com/run/docs/resource-model#worker-pools) through the Cloud Run admin API. A Worker Pool is its own Cloud Run resource type, distinct from a Service or a Job. | ## How Serverless invocation works {/* #how-invocation-works */} @@ -167,6 +169,7 @@ short-lived invocations on AWS Lambda, or long-lived pool instances on GCP Cloud Refer to the lifecycle section for your compute provider: - [AWS Lambda lifecycle](/serverless-workers/aws-lambda#lifecycle) +- [Amazon Bedrock AgentCore Runtime lifecycle](/serverless-workers/agentcore#lifecycle) - [GCP Cloud Run lifecycle](/serverless-workers/cloud-run#lifecycle) ## Failure handling {/* #failure-handling */} @@ -207,9 +210,9 @@ With single-slot configuration, each Activity gets a dedicated execution environ | Constraint | Detail | | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Activity duration | Depends on the compute provider. On AWS Lambda, an Activity must finish within the invocation limit (15 minutes maximum), minus the shutdown deadline buffer. On GCP Cloud Run, instances are long-lived, so no per-invocation limit applies. See [Worker lifecycle](#worker-lifecycle). | +| Activity duration | Depends on the compute provider. On AWS Lambda, an Activity must finish within the invocation limit (15 minutes maximum), minus the shutdown deadline buffer. On Amazon Bedrock AgentCore Runtime, a microVM session has a maximum lifetime of 8 hours. On GCP Cloud Run, instances are long-lived, so no per-invocation limit applies. See [Worker lifecycle](#worker-lifecycle). | | Workflow duration | No limit. Workflows of any duration work. A Workflow runs across as many Workers as needed. | -| Worker code | Same Temporal SDK Worker code, using the serverless Worker package for your SDK. | +| Worker code | Depends on the compute provider. AWS Lambda uses a serverless Worker package for your SDK. Amazon Bedrock AgentCore Runtime and GCP Cloud Run run standard long-lived Temporal Workers inside provider-specific runtime infrastructure. | | Versioning | [Worker Versioning](/worker-versioning) is required. Each Workflow must have an `AutoUpgrade` or `Pinned` behavior, set per-Workflow or as a Worker-level default. See [Worker Versioning](/worker-versioning) for rollout strategies such as ramping, and [Worker Versioning with Serverless Workers](#worker-versioning-with-serverless-workers) for how Worker Deployment Versions map to compute provider primitives. | | High Availability | On failover of a Namespace with [Multi-region or Multi-cloud Replication](/cloud/high-availability), the WCI keeps invoking Workers in the original region unless you manually repoint the compute provider. Compute provider configuration, such as a Lambda ARN or a Cloud Run Worker Pool, is scoped to a single region. See [Serverless Workers and High Availability](/cloud/high-availability#serverless-workers). | @@ -221,4 +224,5 @@ How Worker Deployment Versions map to compute provider primitives differs by pro Refer to the versioning section for your compute provider: - [AWS Lambda versioning](/serverless-workers/aws-lambda#worker-versioning) +- [Amazon Bedrock AgentCore Runtime versioning](/serverless-workers/agentcore#worker-versioning) - [GCP Cloud Run versioning](/serverless-workers/cloud-run#worker-versioning) diff --git a/docs/encyclopedia/workers/serverless-workers/serverless-workers-agentcore.mdx b/docs/encyclopedia/workers/serverless-workers/serverless-workers-agentcore.mdx new file mode 100644 index 0000000000..6f0577e42a --- /dev/null +++ b/docs/encyclopedia/workers/serverless-workers/serverless-workers-agentcore.mdx @@ -0,0 +1,109 @@ +--- +id: serverless-workers-agentcore +title: Serverless Workers on Amazon Bedrock AgentCore Runtime +sidebar_label: Amazon Bedrock AgentCore +description: + How Serverless Workers run on Amazon Bedrock AgentCore Runtime, including Worker Versioning and Runtime session + lifecycle. +slug: /serverless-workers/agentcore +toc_max_heading_level: 4 +tags: + - Workers + - Concepts + - Serverless + - Amazon Bedrock AgentCore +--- + +import { ReleaseNoteHeader } from '@site/src/components'; + + + Amazon Bedrock AgentCore Runtime support is in Pre-release, and its APIs may change in backwards-incompatible ways. + + +This page covers how Serverless Workers run on Amazon Bedrock AgentCore Runtime, including Worker Versioning and the +Runtime session lifecycle. + +To deploy a Worker, see +[Deploy a Serverless Worker on Amazon Bedrock AgentCore Runtime](/production-deployment/worker-deployments/serverless-workers/agentcore). + +On AgentCore Runtime, a Serverless Worker is a standard long-running Temporal Worker that runs inside an AgentCore +Runtime session. When the [Worker Controller Instance (WCI)](/serverless-workers#worker-controller-instance) needs +capacity, it invokes an AgentCore Runtime endpoint. The Runtime starts a Worker, which connects to the Temporal Service +and polls its Task Queue. + +## Autoscaling {/* #autoscaling */} + +AgentCore Runtime uses the same event-driven autoscaling model as AWS Lambda. The WCI invokes individual Runtime +sessions when it needs more capacity. It does not manage a target-sized pool of Runtime sessions. For the shared +autoscaling behavior, see [Autoscaling for Serverless Workers on AWS Lambda](/serverless-workers/aws-lambda#autoscaling). + +## Worker Versioning {/* #worker-versioning */} + +Serverless Workers require [Worker Versioning](/worker-versioning). Associate each Worker Deployment Version with a +named AgentCore Runtime endpoint that points to one AgentCore Runtime version. + +AgentCore creates an immutable Runtime version when you create or update a Runtime. A named endpoint has a stable ARN +and points to a chosen Runtime version. Configure the endpoint ARN as the compute provider for the corresponding Worker +Deployment Version: + +```bash +temporal worker deployment create-version \ + --deployment-name my-worker \ + --build-id v1 \ + --aws-agentcore-endpoint-arn \ + --aws-agentcore-assume-role-arn \ + --aws-agentcore-assume-role-external-id +``` + +Use one named endpoint for each Worker Deployment Version. For example, point an endpoint named `temporal-v1` at +AgentCore Runtime version `1` and use its ARN for Temporal Worker Deployment Version `my-worker/v1`. + +When you deploy new Worker code, AgentCore creates a new Runtime version. Create another endpoint that points to that +new Runtime version and configure it on a new Worker Deployment Version. Keep the older endpoint while Pinned +Workflows can still need the older Worker code. + +:::caution + +Do not configure a live Worker Deployment Version with AgentCore's `DEFAULT` endpoint. That endpoint moves to the +latest Runtime version whenever you update the Runtime. Updating code behind a Worker Deployment Version can cause +non-determinism errors for in-flight Workflows, including Pinned Workflows. + +::: + +For details about AgentCore Runtime versions and endpoints, see [AgentCore Runtime versioning and +endpoints](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/agent-runtime-versioning.html). + +## Lifecycle {/* #lifecycle */} + +An AgentCore Runtime session is the compute that runs a Worker, not a durable place to store Workflow state. AgentCore +can resume a session on new compute after the previous compute ends, and a later Task can run on another Worker. Keep +state that a Workflow needs in the Workflow or another durable store. + +Unlike an AWS Lambda Worker, an AgentCore Worker does not have a fixed Lambda invocation deadline. Your Runtime handler +starts the Worker as background work. The Worker polls until it drains or AgentCore ends its compute. + +Two sets of controls determine when that Worker stops: + +- **Worker idle and graceful-shutdown policy**: Your Worker implementation decides when it has been idle, stops + polling, and waits for in-flight Activities to complete. +- **AgentCore lifecycle settings**: AgentCore can end the session or its compute before the Worker policy does. + +The AgentCore lifecycle settings are: + +- **Idle Runtime session timeout**: Ends a Runtime session after it has not received an AgentCore Runtime invocation for + the configured duration. The default is 15 minutes. This is not a Temporal Worker idle timer: polling the Temporal + Service does not reset it. +- **Maximum lifetime**: Ends the compute running a Runtime session after the configured duration. The default and + maximum is 8 hours. AgentCore can resume the session on new compute after that. + +AgentCore's session idle timeout does not replace a Worker idle policy. It resets with AgentCore Runtime invocations +and does not measure Task Queue activity. To control how long an unused Worker polls, implement a separate shutdown +policy: when its idle condition is met, stop polling and drain in-flight Activities before the Runtime handler returns. +Choose the idle period and drain timeout for your workload, and account for the AgentCore maximum lifetime. + +Configure Activity timeouts and, for long-running Activities, +[Activity Heartbeats](/encyclopedia/detecting-activity-failures#activity-heartbeat) so a retry can recover if AgentCore +ends the compute before an Activity completes. + +For the lifecycle setting ranges and defaults, see [Configure Amazon Bedrock AgentCore lifecycle +settings](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-lifecycle-settings.html). diff --git a/docs/guides/durable-agent-on-agentcore.mdx b/docs/guides/durable-agent-on-agentcore.mdx new file mode 100644 index 0000000000..08ec89d706 --- /dev/null +++ b/docs/guides/durable-agent-on-agentcore.mdx @@ -0,0 +1,394 @@ +--- +id: durable-agent-on-agentcore +title: Build a durable agent on Amazon Bedrock AgentCore +sidebar_label: Durable agent on AgentCore +description: Run a Strands agent as a Temporal Workflow while AgentCore Runtime supplies serverless Worker compute and a code execution tool. +toc_max_heading_level: 3 +author: n/a +tags: + - Workflows + - Activities + - Workers + - Python SDK + - Strands Agents + - Serverless +--- + +import { CaptionedImage, ReleaseNoteHeader } from '@site/src/components'; + + + Amazon Bedrock AgentCore Runtime support is in Pre-release, and its APIs may change in backwards-incompatible ways. + + +This guide deploys a Strands agent as a Temporal Serverless Worker on Amazon Bedrock AgentCore Runtime. The agent uses +Amazon Bedrock for model inference and AgentCore Code Interpreter to run Python. + +The [AgentCore deployment page](/production-deployment/worker-deployments/serverless-workers/agentcore) is a focused +procedure for deploying an existing Worker. This guide uses a complete agent sample to explain why the Workflow, +Activities, Runtime, and Worker Deployment are structured this way. + +## What you will build + +The sample accepts one prompt and runs one Workflow Execution. The Workflow asks the model to answer the prompt. The +model can call Code Interpreter through a Temporal Activity before returning its answer. + +Your local client starts the Workflow through Temporal. When the Task Queue needs a Worker, Temporal starts an +AgentCore Runtime session. The Worker processes the Workflow and Activity Tasks, then drains after 60 seconds without +an Activity starting or finishing. + +The sample is intentionally one turn. This keeps the deployment path visible while still demonstrating the important +reliability boundary: the model and tool calls are recorded Temporal operations, and the Worker process that performs +them can be replaced. + +## Architecture + +Temporal Cloud owns the agent's execution state and capacity control. The application starts or signals the Workflow. +The Workflow records agent decisions and schedules model and tool work as Temporal Tasks. When the Task Queue needs +capacity, the Worker Controller Instance starts AgentCore Runtime sessions. + +Each Runtime session hosts a Temporal Worker that polls the versioned Task Queue. Workers can call AgentCore services, +but the sessions and their process-local state remain replaceable. The sample in this guide uses AgentCore Code +Interpreter. It does not use every AgentCore service shown in the reference architecture. + + + +Place state according to how long it must remain available: + +| State | Location | Reason | +|---|---|---| +| Agent progress and bounded working context | Temporal Workflow | Temporal reconstructs Workflow state from Event History when another Worker continues the execution. | +| Model calls and tool operations | Temporal Activities | Each operation gets its own timeout, Retry Policy, and recorded result. | +| Large conversations, uploads, and generated artifacts | External durable storage, with references in the Workflow | Large or unbounded data should not cause Event History to grow without limit. | +| Process-local caches and temporary files | AgentCore Runtime session | Runtime sessions are replaceable, so the agent must tolerate losing this state. | + +This division is what lets the Workflow outlive any one Runtime session. A Worker can stop after the current work is +complete, and a later Worker can reconstruct the Workflow before continuing it. + +To extend the sample to multiple turns, use one Workflow Id per conversation, keep the Workflow open, and accept later +prompts through Workflow Updates while allowing any compatible Runtime session to process each turn. + +## Prerequisites + +- A Temporal Cloud account with an AWS-hosted Namespace and access to the AgentCore Serverless Workers Pre-release. +- A Temporal Cloud API key that can connect to the Namespace. +- [Temporal CLI v1.8.3](https://github.com/temporalio/cli/releases/tag/v1.8.3) or later. +- Python 3.10 or later and [`uv`](https://docs.astral.sh/uv/). +- Node.js 20 or later and the + [AgentCore CLI](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/agentcore-get-started-cli.html). +- The [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) configured for your AWS + account. +- The [AWS CDK](https://docs.aws.amazon.com/cdk/v2/guide/getting-started.html) installed and bootstrapped in an + [AgentCore-supported Region](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/agentcore-regions.html). +- AWS permissions to deploy 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). +- Access to the Amazon Bedrock model that Strands selects in the target Region. + +## 1. Get the sample + +Clone the branch from the +[Strands Agent on Bedrock AgentCore sample PR](https://github.com/temporalio/samples-python/pull/360), then install its +Python dependencies: + +```bash +git clone --branch schoeff/strands-agent --single-branch \ + https://github.com/temporalio/samples-python.git +cd samples-python/bedrock_agentcore/strands_agent +uv sync +``` + +The cloned directory contains the Workflow, Activity, Runtime handler, AgentCore configuration, deployment scripts, +and IAM template used throughout this guide. + +The sample uses AgentCore's CodeZip build instead of a container image. AgentCore packages the Python project and runs +it on its managed Python runtime, so this path does not require a Dockerfile. The checked-in files define the +application and Runtime settings. The deployment script generates the AgentCore CDK project when you first run it. + +## 2. Examine the agent Workflow + +The sample creates a `TemporalAgent` with a system prompt and the `execute_code` tool: + + +[bedrock_agentcore/strands_agent/workflows.py](https://github.com/temporalio/samples-python/blob/schoeff/strands-agent/bedrock_agentcore/strands_agent/workflows.py) +```py +@workflow.defn +class StrandsAgentWorkflow: + def __init__(self) -> None: + # Configure with the plugin's default BedrockModel(), custom system + # prompt and code interpreter tool. + self.agent = TemporalAgent( + start_to_close_timeout=timedelta(seconds=60), + system_prompt=SYSTEM_PROMPT, + tools=[ + activity_as_tool( + execute_code, + start_to_close_timeout=timedelta(minutes=2), + ) + ], + ) + + @workflow.run + async def run(self, prompt: str) -> str: + # invoke_async, not agent(prompt) -- the sync form spawns a worker thread the + # Workflow sandbox blocks. + result = await self.agent.invoke_async(prompt) + return str(result) + + +``` + + +`TemporalAgent` adapts the Strands agent loop to run in Workflow code. The Temporal Strands plugin schedules each model +call as an Activity. `activity_as_tool` makes `execute_code` another Activity when the model selects that tool. + +The Workflow owns the sequence of model and tool decisions because that sequence must resume correctly after a +failure. The model calls themselves do not run as ordinary Workflow code. They run as Activities because they perform +network I/O, can fail independently, and are not deterministic. + +The `execute_code` Activity creates a Code Interpreter session using the Workflow Id as its session name: + + +[bedrock_agentcore/strands_agent/activities.py](https://github.com/temporalio/samples-python/blob/schoeff/strands-agent/bedrock_agentcore/strands_agent/activities.py) +```py +# Use AgentCore Code Interpreter to provide a code sandbox and execute LLM generated solution +@activity.defn +def execute_code( + code: str, language: LanguageType = LanguageType.PYTHON +) -> dict[str, Any]: + """Run code in this Sessions's sandbox (workflow ID) and return the Code Interpreter result.""" + interpreter = AgentCoreCodeInterpreter( + region=os.environ.get("AWS_REGION", "us-west-2"), + session_name=activity.info().workflow_id, + ) + return interpreter.execute_code( + ExecuteCodeAction(type="executeCode", code=code, language=language) + ) + + +``` + + +Using the Workflow Id gives each Workflow Execution its own Code Interpreter sandbox. + +## 3. Configure and deploy the Runtime + +Install the AgentCore CLI: + +```bash +npm install -g @aws/agentcore +``` + +Open `agentcore/aws-targets.json`. Replace the account number and Region with the AWS account and Region where you +will deploy the Runtime: + +```json +[ + { + "name": "default", + "description": "AWS account and Region for the Runtime", + "account": "", + "region": "" + } +] +``` + +Open `agentcore/agentcore.json` and replace the placeholder values for `TEMPORAL_ADDRESS`, `TEMPORAL_NAMESPACE`, and +`TEMPORAL_API_KEY`. Set `AWS_REGION` to the same Region used in `aws-targets.json`. Keep these sample values unchanged: + +| Setting | Value | +|---|---| +| `TEMPORAL_TASK_QUEUE` | `agentcore-strands-task-queue` | +| `TEMPORAL_DEPLOYMENT_NAME` | `agentcore-strands-agent-python` | +| `TEMPORAL_BUILD_ID` | `1.0.0` | +| Runtime endpoint name | `temporal` | + +These values connect two separately configured systems. The Runtime uses the Task Queue, deployment name, and Build ID +when its Worker registers with Temporal. The Worker Deployment Version created in Step 5 uses the same deployment name +and Build ID and points Temporal back to this Runtime endpoint. If the values differ, Temporal can start compute that +does not register as the version waiting for work. + +Putting the API key in `agentcore.json` keeps the tutorial short. Do not commit the populated file. For a production +deployment, store the key in AWS Secrets Manager and load it when the Runtime starts. + +Export the same connection values for the Temporal CLI and the sample client: + +```bash +export TEMPORAL_ADDRESS="..tmprl.cloud:7233" +export TEMPORAL_NAMESPACE="." +printf "Temporal Cloud API key: " +read -rs TEMPORAL_API_KEY +printf "\n" +export TEMPORAL_API_KEY +export AWS_REGION="" +``` + +Deploy the Runtime and its named endpoint: + +```bash +./bin/create-runtime.sh +``` + +The script creates the AgentCore CDK project on its first run, validates the configuration, packages the sample, and +deploys it. The sample uses public network mode so the Worker can make an outbound connection to Temporal Cloud. The +named endpoint is for capacity requests from Temporal, not prompts from the application. + +Retrieve the Runtime and endpoint ARNs: + +```bash +export AGENT_RUNTIME_ARN="$( + aws bedrock-agentcore-control list-agent-runtimes \ + --region "$AWS_REGION" \ + --query "agentRuntimes[?agentRuntimeName=='TemporalStrandsAgent_temporal_strands_worker'].agentRuntimeArn | [0]" \ + --output text +)" +export AGENT_RUNTIME_ID="${AGENT_RUNTIME_ARN##*/}" +export RUNTIME_ENDPOINT_ARN="$( + aws bedrock-agentcore-control list-agent-runtime-endpoints \ + --agent-runtime-id "$AGENT_RUNTIME_ID" \ + --region "$AWS_REGION" \ + --query "runtimeEndpoints[?name=='temporal'].agentRuntimeEndpointArn | [0]" \ + --output text +)" +echo "$AGENT_RUNTIME_ARN" +echo "$RUNTIME_ENDPOINT_ARN" +``` + +Both commands must print an ARN before you continue. + +AgentCore creates an immutable Runtime version when you deploy changed Worker code or configuration. The named +`temporal` endpoint remains on its configured version. When you redeploy the sample, increment +`endpoints.temporal.version` in `agentcore/agentcore.json` so the endpoint uses the new Runtime version. + +Verify the endpoint version before creating the Worker Deployment Version: + +```bash +aws bedrock-agentcore-control get-agent-runtime-endpoint \ + --agent-runtime-id "$AGENT_RUNTIME_ID" \ + --endpoint-name temporal \ + --query '{status:status,liveVersion:liveVersion}' \ + --region "$AWS_REGION" +``` + +If the endpoint remains on an earlier version, Temporal starts the old 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 details, see [AgentCore Runtime versioning and +endpoints](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/agent-runtime-versioning.html). + +## 4. Grant Temporal access to the Runtime + +Choose an External ID, then use the sample's CloudFormation script to create the IAM role that Temporal Cloud assumes: + +```bash +export EXTERNAL_ID="$(openssl rand -hex 16)" +export INVOCATION_STACK="ac-strands-invoke" + +./bin/mk-invoke-role.sh \ + "$INVOCATION_STACK" \ + "$EXTERNAL_ID" \ + "${AGENT_RUNTIME_ARN}*" +``` + +:::caution + +The sample names the IAM role `Temporal-Cloud-Serverless-Worker-`. An [IAM role name can contain at most 64 +characters](https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-iam-role.html). Keep +`INVOCATION_STACK` to 31 characters or fewer. The `ac-strands-invoke` value above is within the limit. + +::: + +Wait for the stack and retrieve the role ARN: + +```bash +aws cloudformation wait stack-create-complete \ + --stack-name "$INVOCATION_STACK" \ + --region "$AWS_REGION" + +export INVOCATION_ROLE_ARN="$( + aws cloudformation describe-stacks \ + --stack-name "$INVOCATION_STACK" \ + --query 'Stacks[0].Outputs[?OutputKey==`RoleARN`].OutputValue' \ + --output text \ + --region "$AWS_REGION" +)" +echo "$INVOCATION_ROLE_ARN" +``` + +This invocation role lets Temporal get the named endpoint and invoke the Runtime. It is separate from the Runtime +execution role that AgentCore created to run the Worker and access Code Interpreter. + +Keeping the roles separate gives each side only the permissions it needs. Temporal assumes the invocation role to +start capacity. AgentCore assumes the execution role inside that capacity when the Worker calls Bedrock and Code +Interpreter. The trailing wildcard on the Runtime ARN allows the invocation role to cover the named endpoint as well +as the Runtime. + +## 5. Create the Serverless Worker deployment + +Create a Worker Deployment and a version that points to the AgentCore endpoint: + +```bash +temporal worker deployment create \ + --name agentcore-strands-agent-python + +temporal worker deployment create-version \ + --deployment-name agentcore-strands-agent-python \ + --build-id 1.0.0 \ + --aws-agentcore-endpoint-arn "$RUNTIME_ENDPOINT_ARN" \ + --aws-agentcore-assume-role-arn "$INVOCATION_ROLE_ARN" \ + --aws-agentcore-assume-role-external-id "$EXTERNAL_ID" + +temporal worker deployment set-current-version \ + --deployment-name agentcore-strands-agent-python \ + --build-id 1.0.0 \ + --yes +``` + +Creating the version causes Temporal to invoke the Runtime and wait for the Worker to register. The deployment name +and Build ID match the values in `agentcore.json`. Setting the version as current lets it receive new Tasks on the +`agentcore-strands-task-queue` Task Queue. + +The Worker Deployment Version binds one version of the Worker code to one compute configuration. The sample registers +Workflows with `PINNED` behavior, so a Workflow continues on its assigned version instead of moving to a newer version +while it is running. Marking `1.0.0` as current sends new Workflow Executions to that version. + +## 6. Run the agent + +Run the sample client with its default prompt: + +```bash +uv run python starter.py +``` + +Or provide a prompt: + +```bash +uv run python starter.py \ + "Calculate the first 10 Fibonacci numbers and verify the result with Python." +``` + +`starter.py` starts `StrandsAgentWorkflow` and waits for its result. Temporal starts AgentCore Worker capacity, the +Workflow calls the model and Code Interpreter Activities, and the client prints the answer. The Workflow then +completes. After 60 seconds without an Activity starting or finishing, the Worker drains. + +Starting the agent through `starter.py`, rather than invoking the AgentCore endpoint, is an architectural choice. The +Temporal Client creates the durable Workflow Execution first. AgentCore supplies a Worker when Temporal has a Task +ready to run. + +Inspect the completed Workflow Execution: + +```bash +temporal workflow show \ + --workflow-id agentcore-strands-workflow-id-1 +``` + +The Event History contains the model and `execute_code` Activities. Follow the Worker from AgentCore: + +```bash +agentcore logs --runtime temporal_strands_worker +``` + +The Workflow history and AgentCore logs show the two sides of the integration. Event History records what the agent +did. The AgentCore logs show which replaceable Worker process performed the work and when that Worker drained. diff --git a/docs/production-deployment/worker-deployments/index.mdx b/docs/production-deployment/worker-deployments/index.mdx index 4a4ecb9a1f..f70742770f 100644 --- a/docs/production-deployment/worker-deployments/index.mdx +++ b/docs/production-deployment/worker-deployments/index.mdx @@ -30,7 +30,7 @@ You can optionally use the Temporal [Worker Controller](/production-deployment/w This section also covers specific Worker Deployment examples: - [**Serverless Workers**](/production-deployment/worker-deployments/serverless-workers) - Deploy Serverless Workers on serverless compute like AWS Lambda. + Deploy Serverless Workers on AWS Lambda, GCP Cloud Run, or Amazon Bedrock AgentCore Runtime. Temporal invokes your Worker when Tasks arrive, with no long-lived processes to manage. - [**Deploy Workers to Amazon EKS**](/production-deployment/worker-deployments/deploy-workers-to-aws-eks) diff --git a/docs/production-deployment/worker-deployments/serverless-workers/agentcore.mdx b/docs/production-deployment/worker-deployments/serverless-workers/agentcore.mdx new file mode 100644 index 0000000000..27a92f8602 --- /dev/null +++ b/docs/production-deployment/worker-deployments/serverless-workers/agentcore.mdx @@ -0,0 +1,304 @@ +--- +id: agentcore +title: Deploy a Serverless Worker on Amazon Bedrock AgentCore Runtime +sidebar_label: Amazon Bedrock AgentCore +description: Deploy an existing Python Worker to AgentCore Runtime and configure Temporal Cloud to start capacity when Task Queue demand increases. +slug: /production-deployment/worker-deployments/serverless-workers/agentcore +toc_max_heading_level: 4 +tags: + - Workers + - Deploy + - Serverless + - Amazon Bedrock AgentCore +--- + +import { ReleaseNoteHeader } from '@site/src/components'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + + + Amazon Bedrock AgentCore Runtime support is in Pre-release, and its APIs may change in backwards-incompatible ways. + + +This page covers only deploying an existing Python [Serverless Worker](/serverless-workers) to Amazon Bedrock AgentCore +Runtime and connecting it to a Worker Deployment Version. It assumes that your Worker and AgentCore project are already +in place. + +For a complete tutorial, 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 {/* #prerequisites */} + +- A Temporal Cloud account with an AWS-hosted Namespace and access to the AgentCore Serverless Workers Pre-release. +- A Temporal Cloud API key that can connect to the Namespace. +- [Temporal CLI v1.8.3](https://github.com/temporalio/cli/releases/tag/v1.8.3) or later, configured for your Namespace. +- An existing Python Temporal Worker with an + [AgentCore Runtime handler](/develop/python/workers/serverless-workers/agentcore#runtime-handler). +- An AgentCore project that packages the Worker and contains `agentcore/agentcore.json`, `agentcore/aws-targets.json`, + and the generated AgentCore CDK project. +- An AWS account in an [AgentCore-supported Region](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/agentcore-regions.html). +- The [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) installed and configured + with credentials for that account. +- Node.js 20 or later and the [AgentCore CLI](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/agentcore-get-started-cli.html) + installed with `npm install -g @aws/agentcore`. +- The [AWS CDK](https://docs.aws.amazon.com/cdk/v2/guide/getting-started.html) installed and bootstrapped in the target + account and Region. +- 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). + +## 1. Configure the Worker Runtime {/* #configure-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. In the existing Runtime object, configure +the Temporal connection, Task Queue, Worker Deployment name, and Build ID: + +```json +{ + "name": "TEMPORAL_ADDRESS", + "value": "..tmprl.cloud:7233" +}, +{ + "name": "TEMPORAL_NAMESPACE", + "value": "." +}, +{ + "name": "TEMPORAL_API_KEY", + "value": "" +}, +{ + "name": "TEMPORAL_TASK_QUEUE", + "value": "" +}, +{ + "name": "TEMPORAL_DEPLOYMENT_NAME", + "value": "" +}, +{ + "name": "TEMPORAL_BUILD_ID", + "value": "" +} +``` + +The Task Queue must match the Task Queue used by your application. The deployment name and Build ID must match the +Worker Deployment Version that you create in [Step 4](#create-worker-deployment-version). + +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 +{ + "entrypoint": "agentcore_worker.py", + "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 2](#deploy-runtime). + +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. Deploy the Worker Runtime {/* #deploy-runtime */} + +From the AgentCore project directory, validate and deploy the project: + +```bash +agentcore validate +agentcore deploy --target -y +``` + +AgentCore packages the Worker and its dependencies, deploys the Runtime, and creates the named endpoint. + +Check the deployed resources: + +```bash +agentcore status --runtime --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 Cloud. + +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 \ + --endpoint-name \ + --query '{status:status,liveVersion:liveVersion}' \ + --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 your Temporal Cloud Namespace. +Temporal invokes the named endpoint by assuming the IAM role that you create in [Step 3](#configure-iam). + +## 3. Grant Temporal permission to invoke the Runtime {/* #configure-iam */} + +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 `-`. 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 \ + --template-body file://temporal-cloud-serverless-worker-agentcore-role.yaml \ + --parameters \ + ParameterKey=AssumeRoleExternalId,ParameterValue= \ + ParameterKey=AgentRuntimeARNs,ParameterValue='*' \ + ParameterKey=RoleName,ParameterValue= \ + --capabilities CAPABILITY_NAMED_IAM \ + --region +``` + +Wait for the CloudFormation stack to finish: + +```bash +aws cloudformation wait stack-create-complete \ + --stack-name \ + --region +``` + +Then retrieve the invocation role ARN: + +```bash +aws cloudformation describe-stacks \ + --stack-name \ + --query 'Stacks[0].Outputs[?OutputKey==`RoleARN`].OutputValue' \ + --output text \ + --region +``` + +The role grants `bedrock-agentcore:InvokeAgentRuntime` and `bedrock-agentcore:GetAgentRuntimeEndpoint` on the configured +Runtime resources. This role does not run the Worker code. + +## 4. Create the Worker Deployment Version {/* #create-worker-deployment-version */} + +Create a [Worker Deployment Version](/production-deployment/worker-deployments/worker-versioning) whose compute +configuration points to the named AgentCore Runtime endpoint. + + + + +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 2](#deploy-runtime). +- **IAM role ARN**: the invocation role ARN from [Step 3](#configure-iam). +- **External ID**: the External ID from [Step 3](#configure-iam). + +Save the Worker Deployment. When you create a version through the UI, the version is automatically current. Continue +to [Step 6](#verify-worker-startup). + + + + +First, create the Worker Deployment if it does not already exist: + +```bash +temporal worker deployment create \ + --namespace \ + --name +``` + +Then create the version with the AgentCore compute configuration: + +```bash +temporal worker deployment create-version \ + --namespace \ + --deployment-name \ + --build-id \ + --aws-agentcore-endpoint-arn \ + --aws-agentcore-assume-role-arn \ + --aws-agentcore-assume-role-external-id +``` + +The deployment name and Build ID must match the values in the Runtime environment. + + + + +To check whether Temporal can reach the endpoint, open the Worker Deployment Version in the Temporal Cloud UI and +select **Actions** > **Validate Connection**. This checks that Temporal can assume the invocation role, get the named +endpoint, and invoke the Runtime. + +## 5. Set the version as current {/* #set-current-version */} + +If you used the Temporal CLI, set the version as current: + +```bash +temporal worker deployment set-current-version \ + --namespace \ + --deployment-name \ + --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. + +## 6. Verify Worker startup {/* #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 Cloud UI**: Open the Worker Deployment Version and confirm that the connection is valid and a Worker has + polled the Task Queue. +- **AgentCore logs**: Run `agentcore logs --runtime ` to see the Worker start and process Tasks. +- **Temporal CLI**: Run `temporal worker deployment describe --name ` to inspect the deployment and + current version. diff --git a/docs/production-deployment/worker-deployments/serverless-workers/index.mdx b/docs/production-deployment/worker-deployments/serverless-workers/index.mdx index 1a80f95fcc..8e15a56982 100644 --- a/docs/production-deployment/worker-deployments/serverless-workers/index.mdx +++ b/docs/production-deployment/worker-deployments/serverless-workers/index.mdx @@ -15,10 +15,9 @@ tags: import { ReleaseNoteHeader } from '@site/src/components'; - 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. Support for GCP Cloud Run and Amazon Bedrock AgentCore Runtime is in + Pre-release, and their APIs may change in backwards-incompatible ways. To request access, create a + [support ticket](/cloud/support#support-ticket) or contact your account team. Serverless Workers let you run Temporal Workers on serverless compute. Deploy your Worker code to a serverless provider, @@ -27,9 +26,8 @@ work on the Task Queue. There is no always-on Worker fleet to provision or scale Temporal monitors Task Queues that have a compute provider configured. When a Task arrives and no Worker is free to take it, the [Worker Controller Instance (WCI)](/serverless-workers#how-invocation-works) starts compute. How it starts -compute is where the providers differ. On AWS Lambda the WCI invokes a function per unit of work, and the Worker exits -when the invocation window ends. On GCP Cloud Run it resizes a Worker Pool of long-lived instances that poll -continuously. +compute is where the providers differ. On AWS Lambda and AgentCore Runtime, the WCI invokes compute in response to +unmet Task Queue demand. On GCP Cloud Run, it resizes a Worker Pool of long-lived instances that poll continuously. ## Supported providers @@ -38,3 +36,6 @@ continuously. - [**GCP Cloud Run**](/production-deployment/worker-deployments/serverless-workers/cloud-run) - Deploy a Serverless Worker to a Cloud Run Worker Pool. Temporal impersonates a service account in your GCP project to scale the pool as Tasks arrive and drain. +- [**Amazon Bedrock AgentCore Runtime**](/production-deployment/worker-deployments/serverless-workers/agentcore) - + Deploy a Serverless Worker to AgentCore Runtime. Temporal assumes an IAM role in your AWS account to invoke the + Runtime endpoint as Tasks arrive. diff --git a/sidebars.js b/sidebars.js index 8f8e2b6233..9cd04eb268 100644 --- a/sidebars.js +++ b/sidebars.js @@ -640,6 +640,7 @@ const developPythonCategory = { }, items: [ 'develop/python/workers/serverless-workers/aws-lambda', + 'develop/python/workers/serverless-workers/agentcore', 'develop/python/workers/serverless-workers/cloud-run', ], }, @@ -1604,6 +1605,7 @@ module.exports = { 'production-deployment/worker-deployments/serverless-workers/aws-lambda/self-hosted-setup', ], }, + 'production-deployment/worker-deployments/serverless-workers/agentcore', { type: 'category', label: 'GCP Cloud Run', @@ -2014,6 +2016,7 @@ module.exports = { link: { type: 'doc', id: 'encyclopedia/workers/serverless-workers/serverless-workers' }, items: [ 'encyclopedia/workers/serverless-workers/serverless-workers-aws-lambda', + 'encyclopedia/workers/serverless-workers/serverless-workers-agentcore', 'encyclopedia/workers/serverless-workers/serverless-workers-cloud-run', ], }, @@ -2176,6 +2179,7 @@ module.exports = { id: 'guides/index', }, items: [ + 'guides/durable-agent-on-agentcore', 'guides/entity-pattern-loyalty-points', 'guides/recover-without-restart', 'guides/route-specialized-workloads', diff --git a/snipsync.config.yaml b/snipsync.config.yaml index a1b6ba5c4f..7598622999 100644 --- a/snipsync.config.yaml +++ b/snipsync.config.yaml @@ -7,6 +7,8 @@ origins: repo: samples-typescript - owner: temporalio repo: samples-python + # Remove this ref after temporalio/samples-python#360 merges. + ref: schoeff/strands-agent - owner: temporalio repo: reference-app-orders-go - owner: temporalio @@ -39,7 +41,6 @@ origins: ref: 'main' - owner: temporalio repo: sdk-go - targets: - docs diff --git a/src/components/GuidesGrid/guides-data.json b/src/components/GuidesGrid/guides-data.json index 9f05d5194e..8e6641a888 100644 --- a/src/components/GuidesGrid/guides-data.json +++ b/src/components/GuidesGrid/guides-data.json @@ -1,4 +1,13 @@ [ + { + "name": "Durable agent on AgentCore", + "description": + "Run a long-lived Strands agent with Temporal and serverless Worker compute on Amazon Bedrock AgentCore.", + "tags": ["AI agents"], + "sdk": "Python", + "href": "/guides/durable-agent-on-agentcore" + }, + { "name": "Customer loyalty program", "description": diff --git a/static/diagrams/temporal-agentcore-reference-architecture.png b/static/diagrams/temporal-agentcore-reference-architecture.png new file mode 100644 index 0000000000..b429235b03 Binary files /dev/null and b/static/diagrams/temporal-agentcore-reference-architecture.png differ diff --git a/static/files/temporal-cloud-serverless-worker-agentcore-role.yaml b/static/files/temporal-cloud-serverless-worker-agentcore-role.yaml new file mode 100644 index 0000000000..a4d5b16504 --- /dev/null +++ b/static/files/temporal-cloud-serverless-worker-agentcore-role.yaml @@ -0,0 +1,69 @@ +# CloudFormation template for creating an IAM role that Temporal Cloud can assume to invoke AgentCore runtimes. +AWSTemplateFormatVersion: '2010-09-09' +Description: + Creates an IAM role that Temporal Cloud can assume to invoke Amazon Bedrock AgentCore runtimes for Serverless Workers. + +Parameters: + AssumeRoleExternalId: + Type: String + Description: A string you choose. Use the same value when creating the Worker Deployment Version. + AllowedPattern: '[a-zA-Z0-9_+=,.@-]*' + MinLength: 5 + MaxLength: 45 + + AgentRuntimeARNs: + Type: CommaDelimitedList + Description: >- + Comma-separated list of AgentCore Runtime ARNs that Temporal may invoke. Append a wildcard to each Runtime ARN + to include its endpoints. + + RoleName: + Type: String + Default: 'Temporal-Cloud-Serverless-Worker' + +Resources: + TemporalCloudServerlessWorker: + Type: AWS::IAM::Role + Properties: + RoleName: !Sub '${RoleName}-${AWS::StackName}' + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + AWS: + - arn:aws:iam::902542641901:role/wci-lambda-invoke + - arn:aws:iam::160190466495:role/wci-lambda-invoke + - arn:aws:iam::819232936619:role/wci-lambda-invoke + - arn:aws:iam::829909441867:role/wci-lambda-invoke + - arn:aws:iam::354116250941:role/wci-lambda-invoke + Action: sts:AssumeRole + Condition: + StringEquals: + 'sts:ExternalId': !Ref AssumeRoleExternalId + Description: The role Temporal Cloud uses to invoke AgentCore runtimes for Serverless Workers + MaxSessionDuration: 3600 + + TemporalCloudAgentCoreInvokePermissions: + Type: AWS::IAM::Policy + Properties: + PolicyName: 'Temporal-Cloud-AgentCore-Invoke-Permissions' + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - bedrock-agentcore:InvokeAgentRuntime + - bedrock-agentcore:GetAgentRuntimeEndpoint + Resource: !Ref AgentRuntimeARNs + Roles: + - !Ref TemporalCloudServerlessWorker + +Outputs: + RoleARN: + Description: The ARN of the IAM role created for Temporal Cloud + Value: !GetAtt TemporalCloudServerlessWorker.Arn + + AgentRuntimeARNs: + Description: The AgentCore Runtime ARNs that Temporal may invoke + Value: !Join [', ', !Ref AgentRuntimeARNs]