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 holds the conversation and coordinates each turn. 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 combines several pieces, but each one has a separate responsibility:
| System | Job in this application |
|---|---|
| Application client | Starts and sends prompts to the Workflow through the Temporal Client. |
| Temporal Workflow | Represents one conversation and coordinates its turns, waits, and recovery. |
| Strands Agents | Defines the system prompt, tools, model interaction, and agent loop for each turn. |
| Temporal Worker | Runs Workflow Tasks and Activities from the Task Queue. |
| AgentCore Runtime | Supplies isolated AWS compute that hosts the Worker when the Task Queue needs capacity. |
| Amazon Bedrock | Performs model inference when the Workflow runs a model Activity. |
| AgentCore Code Interpreter | Runs code in a managed sandbox when the model selects that tool. |
A conversation turn moves through those pieces as follows:
- Temporal records the prompt and places a Workflow Task on the Task Queue.
- If no Worker is polling, Temporal starts Worker capacity on AgentCore Runtime.
- The Worker processes the Workflow Task. Strands determines whether the turn needs a model call or tool call, and the Workflow schedules each call as an Activity.
- The Worker returns the answer through the Update. The Workflow then waits without using Worker compute.
- After the Worker becomes idle, it drains and the AgentCore Runtime handler returns.
- A later prompt repeats the process. Another Worker can reconstruct the Workflow from Event History and continue the same conversation.
Keep the conversation in the Workflow
Use the Workflow Id as the durable identity of the conversation. The Workflow holds the Strands message list and the current control state. Temporal records the events needed to reconstruct that state, including prompts and completed model and tool Activity results.
The Workflow is not a continuously running Python process. After a turn, it can wait for the next Update without a Worker assigned to it. When another Workflow Task arrives, any compatible Worker polling the Task Queue can process it. Temporal replays Event History on that Worker before the Workflow continues.
Separate agent behavior from external calls
Strands defines the behavior within a turn. You configure the agent's instructions and available tools, then Strands decides when to call the model, select a tool, or return an answer.
The Temporal Strands plugin changes where those calls execute. Model calls and tools that perform I/O run as Temporal Activities instead of running directly in Workflow code. Temporal records each completed result and gives each call its own timeout, Retry Policy, and failure boundary.
Treat AgentCore Runtime as Worker compute
An AgentCore Runtime session hosts a Temporal Worker. The Runtime session is not the conversation and does not need to remain active while the Workflow waits. One Runtime session can process Tasks for multiple Workflow Executions, and later Tasks for one Workflow Execution can run in another Runtime session.
This means the Worker process and its local files or variables must be safe to replace. State that the conversation needs after replacement belongs in the Workflow or another durable store. AgentCore services such as Code Interpreter and Memory remain available to Activities, but they do not replace the Workflow's execution state.
Place state according to its lifetime
| State | Location | Reason |
|---|---|---|
| Current conversation and agent progress | Temporal Workflow | It must survive Worker and Runtime replacement. |
| Completed model and tool call results | Temporal Event History | Activity results let replay restore completed progress without repeating successful calls. |
| Approvals, timers, and long waits | Temporal Workflow | These are part of the agent's durable control flow. |
| Knowledge shared across conversations | AgentCore Memory, accessed from an Activity | It belongs to the user or application rather than one Workflow Execution. |
| Credentials for AWS and external systems | AgentCore Identity or an AWS secret store | Workflow state should not contain credentials. |
| Tool access and authorization | AgentCore Gateway and Policy | These services control how tools are reached and whether a call is allowed. |
| Temporary Worker caches | AgentCore Runtime session | They can improve performance but must be safe to lose. |
| Code Interpreter variables and files | Code Interpreter session | They last only for that tool session. Store required outputs durably before relying on them later. |
| Large files and datasets | Object storage, with a reference in the Workflow | Event History is not intended for large application objects. |
The Strands message list is Workflow state in this design. Temporal reconstructs it through Event History when another Worker continues the Workflow. Do not use Event History as unlimited chat or object storage. For conversations that accumulate many turns, use Continue-As-New to start a new Event History while carrying forward the messages 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.