Build a durable agent on Amazon Bedrock AgentCore
This guide builds a data-analysis agent that can continue a conversation after the compute running it has stopped. A Temporal Workflow coordinates each turn and preserves the state needed to continue. Strands defines how the agent uses a model and tools. Amazon Bedrock AgentCore Runtime supplies serverless compute for the Temporal Worker, and AgentCore Code Interpreter supplies an isolated environment for running code.
The result separates the lifetime of the agent from the lifetime of its compute. The Workflow can remain open for days or months without keeping an AgentCore Runtime active.
What you will build
The agent has one Workflow Execution for each conversation. The application starts that Workflow and sends prompts to
an ask Update handler through Temporal. It does not send prompts to the AgentCore Runtime endpoint. Each Update
returns the agent's answer to the application.
The agent uses Amazon Bedrock for model inference and AgentCore Code Interpreter for calculations. After a turn, its Worker retires while the Workflow remains open. A later prompt starts new Worker capacity and continues the same conversation.
Architecture
The application sends prompts to a Temporal Workflow through an Update. The Workflow identifies the conversation and records its execution progress. When its Task Queue has work, Serverless Workers starts an AgentCore Runtime session that hosts a Temporal Worker. A later Task can run on a different Runtime session because Temporal reconstructs the Workflow from Event History before it continues.
Strands runs the agent loop for each turn. The Temporal Strands integration schedules model calls and tool calls as Activities. Each call has its own timeout, Retry Policy, and recorded result. After the Worker returns the answer, the Workflow can wait for another prompt without keeping an AgentCore Runtime session active.
Place state according to how long it must remain available:
| State | Location |
|---|---|
| Execution progress and bounded working context | Temporal Workflow |
| Large or unbounded conversation content, uploads, and artifacts | Durable external storage, with stable references in the Workflow |
| Temporary caches and Code Interpreter files | AgentCore Runtime or tool session, when the application can tolerate losing them |
For clarity, this sample keeps its complete Strands message list in Workflow state. That works for a bounded conversation. For a conversation that can grow without a fixed limit, store its raw content externally and carry a reference or bounded summary in the Workflow. Retrieve external content through an Activity before using it in a model call. Use Continue-As-New to start a new Event History with only the references and working context that the next execution needs.
Prerequisites
To build and run the agent locally, you need:
- Python 3.10 or later and
uv. - The Temporal CLI to run a local Temporal development server.
- AWS credentials with access to the Bedrock model selected by Strands and permission to use AgentCore Code Interpreter. The sample includes the required Code Interpreter IAM policy.
To deploy the Worker, you also need:
- A Temporal Cloud account with an AWS-hosted Namespace and access to the AgentCore Serverless Workers Pre-release.
- An AWS account in an AgentCore-supported Region.
- The AWS and AgentCore tools and permissions listed in the Serverless Worker deployment prerequisites.
This guide uses the durable AgentCore sample, which contains the AgentCore project, Runtime handler, IAM policy, and Code Interpreter Activity. Clone the sample repository and install the application dependencies:
git clone --branch docs/durable-agent-agentcore-sample --single-branch \
https://github.com/temporalio/documentation-sdk-code-examples.git
cd documentation-sdk-code-examples/python-agentcore-durable-agent
uv sync
1. Build the agent locally
The durable AgentCore sample
defines execute_code as a Temporal Activity. It uses the Workflow Id as the Code Interpreter session name so two
Workflow Executions handled by the same process do not share a sandbox. The name does not make the sandbox durable
across Worker replacement.
python-agentcore-durable-agent/activities.py
@activity.defn
def execute_code(
code: str, language: LanguageType = LanguageType.PYTHON
) -> dict[str, Any]:
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)
)
The Activity boundary gives the tool call a separate timeout, Retry Policy, and result in Event History. It also keeps AWS calls out of deterministic Workflow code.
Define a Workflow that accepts multiple prompts:
python-agentcore-durable-agent/workflows.py
@workflow.defn
class DurableAgentWorkflow:
def __init__(self) -> None:
self._done = False
self._lock = asyncio.Lock()
self._agent = TemporalAgent(
model="bedrock",
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.update
async def ask(self, prompt: str) -> str:
async with self._lock:
result = await self._agent.invoke_async(prompt)
return str(result).strip()
@workflow.signal
def finish(self) -> None:
self._done = True
@workflow.run
async def run(self) -> None:
await workflow.wait_condition(lambda: self._done)
await workflow.wait_condition(workflow.all_handlers_finished)
TemporalAgent is a Strands Agent adapted to run inside a Workflow. It retains the Strands message list between
calls to invoke_async. The Temporal Strands plugin runs model calls as Activities, and activity_as_tool runs the
Code Interpreter tool as an Activity. Configure retries through Temporal Activity Retry Policies rather than a Strands
retry strategy.
The lock makes the agent process one prompt at a time. The run method waits until the finish Signal arrives, so the
Workflow remains available between turns. This wait is durable and does not keep a Python process running.
Register DurableAgentWorkflow, execute_code, and StrandsPlugin on a local Worker. The
sample Worker
also creates the executor required by the synchronous execute_code Activity:
python-agentcore-durable-agent/local_worker.py
async def main() -> None:
client = await Client.connect(
"localhost:7233",
plugins=[StrandsPlugin()],
)
with ThreadPoolExecutor(max_workers=4) as activity_executor:
worker = Worker(
client,
task_queue=TASK_QUEUE,
workflows=[DurableAgentWorkflow],
activities=[execute_code],
activity_executor=activity_executor,
)
await worker.run()
Start the Temporal development server, then start the Worker in another terminal:
temporal server start-dev
uv run python local_worker.py
The sample's chat client starts a Workflow and sends each prompt as an Update:
python-agentcore-durable-agent/chat.py
async def main() -> None:
client = await Client.connect(
"localhost:7233",
plugins=[StrandsPlugin()],
)
handle = await client.start_workflow(
DurableAgentWorkflow.run,
id=f"durable-agent-{uuid.uuid4()}",
task_queue=TASK_QUEUE,
)
while prompt := input("You: "):
if prompt == "/finish":
await handle.signal(DurableAgentWorkflow.finish)
return
answer = await handle.execute_update(DurableAgentWorkflow.ask, prompt)
print(f"Agent: {answer}")
Run the client in a third terminal:
uv run python chat.py
Ask a question that requires calculation, then ask a follow-up that depends on the first answer. Enter /finish to
close the Workflow. In the Temporal Web UI, the Event History shows the ask Update, model Activities, and
execute_code Activity for each turn.
2. Run the Worker on AgentCore Runtime
Local development uses a continuously running Worker. On AgentCore Runtime, the Worker starts inside the Runtime's HTTP handler and returns when its idle policy decides to release the compute.
The AgentCore Runtime handler registers the DurableAgentWorkflow and execute_code definitions from
Build the agent locally. It adds Worker Versioning and the Activity-based idle tracker from
the Python AgentCore Worker guide, then
runs the Worker inside the Runtime handler:
python-agentcore-durable-agent/agentcore_worker.py
@app.entrypoint
@app.async_task
async def invoke(payload: dict) -> dict:
client = await Client.connect(
required_env("TEMPORAL_ADDRESS"),
namespace=required_env("TEMPORAL_NAMESPACE"),
api_key=required_env("TEMPORAL_API_KEY"),
tls=True,
plugins=[StrandsPlugin()],
)
tracker = ActivityTracker()
with ThreadPoolExecutor(max_workers=4) as activity_executor:
worker = Worker(
client,
task_queue=os.environ.get("TEMPORAL_TASK_QUEUE", TASK_QUEUE),
workflows=[DurableAgentWorkflow],
activities=[execute_code],
activity_executor=activity_executor,
interceptors=[tracker],
deployment_config=WorkerDeploymentConfig(
version=WorkerDeploymentVersion(
deployment_name=os.environ.get(
"TEMPORAL_DEPLOYMENT_NAME", DEPLOYMENT_NAME
),
build_id=os.environ.get("TEMPORAL_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)
return {"message": "Worker drained"}
The invocation payload does not contain a user prompt. Temporal invokes the Runtime endpoint to add Worker capacity. Clients continue to start and message Workflows through the Temporal Client.
The Runtime does not need a copy of the conversation in a local file or global variable. When a new Worker receives a
Workflow Task, Temporal replays the Workflow's Event History and restores the TemporalAgent message list before new
model or tool calls run.
3. Deploy the Serverless Worker
Install the AgentCore CLI and generate the CDK project used by the sample's Runtime definition:
npm install -g @aws/agentcore
./bootstrap-agentcore-project.sh
Follow Deploy a Serverless Worker on Amazon Bedrock AgentCore Runtime to deploy the existing AgentCore project and configure its Worker Deployment Version.
For this application, use the same values in each place:
| Setting | Tutorial value |
|---|---|
| Runtime entrypoint | agentcore_worker.py |
| Task Queue | durable-agent |
| Worker Deployment name | durable-agent-agentcore |
| Build ID | A version for this code, such as 1.0.0 |
The AgentCore Runtime execution role needs permission to invoke Bedrock and Code Interpreter. The separate role that Temporal Cloud assumes needs permission to invoke the AgentCore Runtime endpoint. The deployment guide creates and configures the second role.
4. Talk to the deployed agent
Start one conversation Workflow. This command returns immediately while the Workflow remains open:
temporal workflow start \
--workflow-id durable-agent-alice \
--type DurableAgentWorkflow \
--task-queue durable-agent
Send the first prompt as an Update and wait for the reply:
temporal workflow update execute \
--workflow-id durable-agent-alice \
--name ask \
--input '"A film festival has 7 screens with 4 showings per screen. How many screenings can it schedule?"'
Temporal starts AgentCore Worker capacity because the Task Queue has work. After the turn completes and the idle period expires, the Runtime handler drains the Worker and returns. Confirm this in the AgentCore logs:
agentcore logs --runtime <RUNTIME_NAME>
After the Worker has retired, send a follow-up that depends on the first turn:
temporal workflow update execute \
--workflow-id durable-agent-alice \
--name ask \
--input '"If we add two screenings to the total you calculated, what is the new total?"'
Temporal starts capacity again. The new Worker reconstructs the existing Workflow and its Strands messages, so the agent can interpret "the total you calculated" without depending on the previous Worker process.
End the conversation when it no longer needs to accept prompts:
temporal workflow signal \
--workflow-id durable-agent-alice \
--name finish
5. Test recovery
Worker retirement between turns tests one form of recovery. You can also interrupt compute while a model or tool Activity is running. Start a prompt that takes long enough to observe, find the active Runtime session identifier in the AgentCore logs, and stop that session:
aws bedrock-agentcore stop-runtime-session \
--agent-runtime-arn <AGENT_RUNTIME_ARN> \
--runtime-session-id <RUNTIME_SESSION_ID> \
--region <AWS_REGION>
For the required IAM permission and API behavior, see Stop a running session.
The Activity attempt running on that Worker is interrupted. Temporal keeps the Workflow state and schedules the Activity again according to its Retry Policy. Serverless Workers starts new AgentCore capacity to process the Task. In the Temporal Web UI, inspect the Activity attempts and confirm that the Workflow continues without restarting the conversation.
An Activity can run more than once if its Worker stops after making an external change but before reporting completion. Use an idempotency key for tools that change external state. The Workflow Id plus a stable operation identifier is a common choice. Code execution used only to calculate an answer does not make an external business change, so it is a safe recovery demonstration.