在无服务器流水线中异步调用 Amazon Bedrock AgentCore agent 的模式
英文标题:Asynchronous patterns for calling Amazon Bedrock AgentCore agents in serverless pipelines
本文介绍了在 AWS Step Functions 无服务器流水线中异步调用 Amazon Bedrock AgentCore agent 的三种模式:任务令牌回调、直接服务集成和 Lambda durable function。三种模式都避免了调用方在 agent 推理期间保持阻塞,从而消除 Lambda、容器或 Amazon EC2 等计算资源的空闲等待成本。文中以房地产融资文档验证为例,对比了阻塞式反模式与异步模式,并指出 Step Functions 的 AgentCore 优化集成可完全移除中间 Lambda。测试示例显示,Validate 状态活跃 19.6 秒,而调度 Lambda 的计费时长仅为 4.8 秒。
1.3 MB
101.1 KB
124.1 KB
345.0 KB
141.2 KB
346.5 KB
980.1 KB
34.1 KB
54.3 KB
116.3 KB
中文
在无服务器流水线中异步调用 Amazon Bedrock AgentCore agent 的模式,可以在 AI agent 处理请求期间消除空闲计算成本。一个常见示例是文档验证:在房地产融资后台业务中,agent 可以读取房产记录或贷款合同,推理其中的信息是否完整且一致,并返回供后续步骤执行的结论。Amazon Bedrock AgentCore 提供了一个平台,可使用任意框架或模型大规模构建、连接和优化 agent。
这些 agent 具有传统流水线步骤所没有的特性:它们在给出答案前会思考一段时间。具体时长取决于提示词、模型和文档,但很少能够立即返回;这种延迟会改变调用它们的方式。最常见的初始实现是使用计算服务,例如 AWS Lambda 函数,调用 agent 后等待响应。该函数等待期间不会执行任何工作,但仍处于运行状态,并且每一秒都会产生计费。
理解成本实际落在哪一侧会有所帮助,因为调用双方的计费方式不同。Amazon Bedrock AgentCore runtime 是 Amazon Bedrock AgentCore 的一项能力,采用按消费量计费的模式:agent 空闲时不对 CPU 收费。例如,当它等待大语言模型生成响应,或等待工具或模型上下文协议(MCP)调用返回时,在此期间会对内存收费,但不会对 CPU 收费。调用 agent 的计算服务则没有这种行为。发起同步调用的 Lambda 函数、容器或 Amazon Elastic Compute Cloud(Amazon EC2)实例会一直阻塞,在 agent 返回之前始终占用并支付完整的计算资源配额。因此,浪费并不发生在 agent 一侧,而是发生在调用方:它在一个保持打开的连接上空等。
这使调用方的成本与 agent 的运行时间同步增长。阻塞等待 agent 的函数,实际上会按几乎完整的处理时长计费;而仅启动 agent 后立即返回的函数,则只会按短暂的分发时间计费。解决办法是在等待期间释放调用方的计算资源,并且只在 agent 产出结果时恢复流水线。本文展示三种实现方式(任务令牌回调、直接服务集成和 durable function),并将它们与阻塞式反模式进行对比。
示例流水线
为了在相同条件下比较这些模式,我们让每种模式都经过同一条流水线,只替换调用 agent 的步骤。该流水线是一个刻意简化的虚构场景(为房地产融资验证文档),旨在使编排逻辑更清晰。它并非本文的重点,而是代表任何调用 agent(或其他慢速服务)并基于结果执行后续操作的工作流;你可以将其替换为自己的使用场景。
该流水线包含五个阶段:
- 提取:一个 AWS Lambda 函数对文档执行光学字符识别(OCR)和文本提取。(提取过程为模拟执行,因此该场景无需真实文档。)
- 识别:一个 Lambda 函数对文档进行分类,并设置路由标志(
shouldOrganize、shouldValidate)。 - 路由:一个 Choice 状态根据这些标志引导流程。
- 整理与验证:一个 Parallel 状态在整理文档的同时,在独立分支中由 Amazon Bedrock AgentCore agent 对文档进行验证。这个 Validate 分支是不同模式之间唯一会变化的部分。
- 结果:一个 Lambda 函数处理 agent 的结论,并决定下一步操作(批准或退回更正)。
下图展示该流水线。在所有情形中,流水线保持不变;仅替换 Validate 分支,以演示每一种调用模式。

图 1:示例流水线。不同模式之间只有高亮显示的 Validate 分支发生变化
同一个 Amazon Bedrock AgentCore agent 服务于全部四种情形。agent 会检查每次调用并决定响应方式:如果收到 AWS Step Functions 的任务令牌,它会在完成时唤醒对应执行;如果收到 durable function 的回调 ID,它会唤醒该 durable function;如果两者都未收到,则会直接在响应中返回结论。这意味着你可以变更编排模式,而无需修改或重新部署 agent。
agent 如何在不阻塞调用方的情况下归还控制权
其机制是 agent 操作组中的一个控制权归还操作。agent 完成推理后,会调用一个 Lambda,将结果和任务令牌回传至 Step Functions。(后文介绍的模式 2 通过让 Step Functions 直接与 AgentCore 集成,完全移除了这个 Lambda。)
以下代码展示该 Lambda 的核心部分:
The tool the agent calls once it reaches a verdict
@tool def conclude_validation(approved: bool, issues: list, summary: str) -> str: verdict = {"approved": approved, "issues": issues, "summary": summary, "source": "agentcore"}
# A Step Functions task token was passed: resume that execution
if task_token:
sfn.send_task_success(taskToken=task_token, output=json.dumps(verdict))
return "Step Functions resumed."
# A durable-function callback ID was passed: resume the durable function
if callback_id:
lambda_client.send_durable_execution_callback_success(
CallbackId=callback_id, Result=json.dumps(verdict).encode("utf-8"))
return "Durable function resumed."
# Neither was passed: this is a synchronous call, return the verdict inline
return "Verdict recorded."
入口函数会基于相同信号决定以后台方式还是同步方式运行:
@app.async_task async def validate_document_async(prompt, document, extracted_text): # Background work; conclude_validation fires the right callback when done agent = build_agent() await agent.invoke_async(message(prompt, document, extracted_text))
@app.entrypoint async def handler(event): task_token = event.get("taskToken") # passed by the task-token pattern callback_id = event.get("callbackId") # passed by the durable-function pattern
# Asynchronous: start the work and return "accepted" right away
if task_token or callback_id:
asyncio.create_task(validate_document_async(...))
return {"status": "accepted"}
# Synchronous: run now and return the verdict in the response
agent = build_agent()
await agent.invoke_async(message(...))
return verdict
agent 准备就绪后,本文其余部分将聚焦于调用它的四种方式。
调用 agent:四种方法
我们先从阻塞式反模式开始,以建立成本基线;随后介绍三种可避免该问题的模式。全文中的代码和基础设施定义均摘自示例,用于说明每种模式。
阻塞式反模式
最直接的实现是在同一个 Lambda 函数中调用 agent 并等待结果。它能够正常运行,而且实现简单,因此十分常见;但在 agent 思考的整个期间,函数都会持续存活。
// The Lambda function blocks here until the agent responds const response = await agentcore.send( new InvokeAgentRuntimeCommand({ agentRuntimeArn: AGENT_RUNTIME_ARN, payload: new TextEncoder().encode(JSON.stringify(payload)), runtimeSessionId: sessionId, }) ); // The function stays alive and billed for the entire time the agent is thinking.
该函数的计费时长最终大约等于 agent 的处理时长。接下来的三种模式会消除这部分空闲成本,但各自存在不同的权衡。特别是,模式 2 使用 Step Functions 针对 AgentCore Harness (InvokeHarness) 的优化集成,从而完全移除 Lambda。
模式 1:使用调度函数的任务令牌回调
该模式在链路中保留 Lambda 函数以承载自定义逻辑,但移除了空闲成本。Step Functions 使用 waitForTaskToken 集成调用该函数,该集成会传递一个任务令牌并暂停执行。函数使用该令牌启动 agent,随后在数秒内返回。执行会保持暂停且不产生计算资源费用,直到 agent 使用该令牌调用 SendTaskSuccess 来恢复执行。
// Start the agent, pass the task token, and return without waiting const response = await agentcore.send( new InvokeAgentRuntimeCommand({ agentRuntimeArn: AGENT_RUNTIME_ARN, payload: new TextEncoder().encode(JSON.stringify({ ...payload, taskToken })), runtimeSessionId: sessionId, }) ); // Returning here does not complete the step. Step Functions stays paused until // the agent calls SendTaskSuccess with this task token. return { dispatched: true };
相应状态会从上下文传递令牌,并设置超时和心跳作为安全机制。这样一来,如果 agent 没有响应,执行会正常失败,而不是无限期暂停:
"ValidateDispatch": { "Type": "Task", "Resource": "arn:aws:states:::lambda:invoke.waitForTaskToken", "Parameters": { "FunctionName": "${ValidateDispatcherFunctionArn}", "Payload": { "taskToken.$": "$$.Task.Token", "document.$": "$.document", "extractedText.$": "$.extract.extractedText", "executionId.$": "$$.Execution.Id" } }, "TimeoutSeconds": 120, "HeartbeatSeconds": 60, "Next": "AgentCoreValidation" }
成本。 Lambda 函数会运行,但只运行到启动 agent 并返回为止:无论 agent 随后需要多久,通常都只是数秒。你为这段短暂的分发过程付费,而非等待过程,因为 agent 工作期间函数已经结束运行。等待由暂停的 Step Functions 执行承担,它不会对空闲计算资源收费。这是与阻塞版本的核心区别:在阻塞版本中,函数的计费时间会跟随 agent 的处理时间增长。
模式 2:直接服务集成
如果不需要围绕 agent 调用执行自定义代码,可以移除调度函数,并将 Lambda 完全移出调用链。Step Functions 可以通过 AWS SDK 服务集成直接调用 Amazon Bedrock AgentCore,因此 agent 的响应会直接流入下一个状态。此时 Validate 分支成为单个 Task 状态:
"ValidateDirect": { "Type": "Task", "Resource": "arn:aws:states:::aws-sdk:bedrockagentcore:invokeAgentRuntime", "Parameters": { "AgentRuntimeArn": "${AgentRuntimeArn}", "RuntimeSessionId.$": "States.Hash($$.Execution.Id, 'SHA-256')", "Payload.$": "States.JsonToString($.prep.agentInput)" }, "ResultSelector": { "raw.$": "$.Response" }, "TimeoutSeconds": 120, "Next": "ParseVerdict" }
成本。 调用链中没有 Lambda 函数,因此无需支付空闲 Lambda 计算费用。Step Functions 承担等待;Standard 工作流按状态转换计费,而不是按等待时长计费,因此处理期间有实际意义的成本主要来自 agent 本身。
模式 3:Lambda durable function
如果希望在一个位置以代码形式表达编排逻辑,而不是使用状态机,Lambda durable function 可以提供相同的成本特性。借助 @aws/durable-execution-sdk-js SDK,流水线阶段可写成 context.step 调用,并行工作可写成 context.parallel,等待 agent 则可写成 context.waitForCallback。在等待期间,函数会暂停,且不会产生计算费用。agent 通过 SendDurableExecutionCallbackSuccess 恢复函数。
// Suspend the function until the agent calls back const result = await ctx.waitForCallback( "validate-agentcore", async (callbackId) => dispatchAgentCore(callbackId, document, extractedText, executionId), { timeout: { seconds: 120 } } );
成本。 单个函数承载整个流水线,但在暂停并等待 agent 时不会产生计算费用。你只需为每次暂停之间的短暂执行时段付费,其成本模型与任务令牌模式相同,而不是为等待时间付费。
衡量差异
重点不在于某个特定数字。agent 的运行时长会随提示词、模型和文档而变化。真正重要的是任务令牌模式中两个数值之间的关系:Validate 状态处于活动状态的时间,与调度函数实际产生计费的时间。我们测试中的一次运行清楚地展示了这种关系:
Step Functions, ValidateDispatch state Returned (TaskSubmitted): 14:08:19 <- the function returned and shut down Resumed (TaskSucceeded): 14:08:34 <- the agent woke the execution State active ........ 19.6s
Lambda, dispatcher function (CloudWatch REPORT) Billed duration ..... 4.8s
Result: the state was active for 19.6s, but the f
English / 原文
Asynchronous invocation patterns for Amazon Bedrock AgentCore agents in serverless pipelines remove idle compute costs while your AI agent processes requests. A common example is document validation: in a real-estate financing back office, an agent can read a property record or loan contract, reason about whether the information is complete and consistent, and return a verdict that downstream steps act on. Amazon Bedrock AgentCore provides a platform to build, connect, and optimize agents at scale, with any framework or model.
These agents introduce a characteristic that traditional pipeline steps do not have: they think for a while before they answer. How long depends on the prompt, the model, and the document, but it’s rarely instant, and that latency changes how you should call it. The most common first implementation is a compute service, such as an AWS Lambda function, that invokes the agent and waits for the response. While that function waits, it does nothing, but it is still running, and you are billed for every second of it.
It helps to see where the cost actually lands, because the two sides of the call are billed differently. Amazon Bedrock AgentCore runtime, a capability of Amazon Bedrock AgentCore, has a consumption-based model that doesn’t charge for CPU while the agent is idle. For instance, while it waits on a large language model to generate a response, or on a tool or Model Context Protocol (MCP) call to return, you are billed for memory during that time, but not for CPU. The compute service that called the agent has no such behavior. A Lambda function, container, or Amazon Elastic Compute Cloud (Amazon EC2) instance that issues a synchronous call sits blocked. It holds (and pays for) its full compute allocation until the agent responds. So the waste is not on the agent side. It’s the caller, idling on an open connection.
That makes the caller’s cost track the agent’s runtime. A function that blocks on the agent is billed for essentially the entire processing time, whereas a function that starts the agent and returns is billed only for the brief dispatch. The fix is to release the caller’s compute during the wait and resume the pipeline only when the agent has a result. In this post, we show three patterns that do this (task-token callback, direct service integration, and durable function) and contrast them with the blocking anti-pattern.
An example pipeline
To compare the patterns on equal footing, we run each one through the same pipeline and change only the step that calls the agent. The pipeline is a deliberately simple, made-up scenario (validating documents for real-estate financing) chosen to keep the orchestration clear. It’s not the point of the post. It stands in for any workflow that calls an agent (or another slow service) and then acts on the result, so picture your own use case in its place.
The pipeline has five stages:
- Extract : An AWS Lambda function performs optical character recognition (OCR) and text extraction on the document. (Extraction is simulated, so the scenario runs without real documents.)
- Identify : A Lambda function classifies the document and sets routing flags (
shouldOrganize,shouldValidate). - Route : A Choice state directs the flow based on those flags.
- Organize and Validate : A Parallel state organizes the document while, in a separate branch, the Amazon Bedrock AgentCore agent validates it. This Validate branch is the only part that changes between patterns.
- Result : A Lambda function processes the agent’s verdict and decides the next action (approve, or return for correction).
The following diagram shows the pipeline. It stays the same in every case. Only the Validate branch is swapped to demonstrate each invocation pattern.

Figure 1: The example pipeline. Only the highlighted Validate branch changes between patterns
A single Amazon Bedrock AgentCore agent serves all four cases. The agent inspects each invocation and chooses how to respond: if it receives an AWS Step Functions task token , it wakes that execution when done. If it receives a durable-function callback ID , it wakes the durable function. If it receives neither, it returns the verdict directly in the response. This means you can change the orchestration pattern without changing or redeploying the agent.
How the agent returns control without blocking the caller
The mechanism is a return-of-control action in the agent’s action group. When the agent finishes reasoning, it calls a Lambda that posts the result and the task token back to Step Functions. (Pattern 2, described later, eliminates this Lambda entirely by having Step Functions integrate directly with AgentCore.)
The following code shows the core of that Lambda:
# The tool the agent calls once it reaches a verdict
@tool
def conclude_validation(approved: bool, issues: list, summary: str) -> str:
verdict = {"approved": approved, "issues": issues,
"summary": summary, "source": "agentcore"}
# A Step Functions task token was passed: resume that execution
if task_token:
sfn.send_task_success(taskToken=task_token, output=json.dumps(verdict))
return "Step Functions resumed."
# A durable-function callback ID was passed: resume the durable function
if callback_id:
lambda_client.send_durable_execution_callback_success(
CallbackId=callback_id, Result=json.dumps(verdict).encode("utf-8"))
return "Durable function resumed."
# Neither was passed: this is a synchronous call, return the verdict inline
return "Verdict recorded."
The entrypoint decides whether to run in the background or synchronously based on the same signals:
@app.async_task
async def validate_document_async(prompt, document, extracted_text):
# Background work; conclude_validation fires the right callback when done
agent = build_agent()
await agent.invoke_async(message(prompt, document, extracted_text))
@app.entrypoint
async def handler(event):
task_token = event.get("taskToken") # passed by the task-token pattern
callback_id = event.get("callbackId") # passed by the durable-function pattern
# Asynchronous: start the work and return "accepted" right away
if task_token or callback_id:
asyncio.create_task(validate_document_async(...))
return {"status": "accepted"}
# Synchronous: run now and return the verdict in the response
agent = build_agent()
await agent.invoke_async(message(...))
return verdict
With the agent in place, the rest of the post focuses on the four ways to call it.
Calling the agent: Four approaches
We start with the blocking anti-pattern to establish the baseline cost, then show the three patterns that avoid it. The code and infrastructure definitions throughout are excerpts from the sample, included to illustrate each pattern.
The blocking anti-pattern
The most direct implementation calls the agent and waits for the answer in the same Lambda function. It works, and it is straightforward to implement, which is why it’s so common, but the function stays alive for the entire time the agent is thinking.
// The Lambda function blocks here until the agent responds
const response = await agentcore.send(
new InvokeAgentRuntimeCommand({
agentRuntimeArn: AGENT_RUNTIME_ARN,
payload: new TextEncoder().encode(JSON.stringify(payload)),
runtimeSessionId: sessionId,
})
);
// The function stays alive and billed for the entire time the agent is thinking.
The function’s billed duration ends up approximately equal to the agent’s processing time. The next three patterns eliminate this idle cost, each making a different trade-off. In particular, Pattern 2 uses the Step Functions optimized integration for AgentCore Harness (InvokeHarness), removing the Lambda entirely.
Pattern 1: Task-token callback with a dispatcher function
This pattern keeps a Lambda function in the path for custom logic but removes the idle cost. Step Functions invokes the function with the waitForTaskToken integration, which passes a task token and then pauses the execution. The function uses the token to start the agent, then returns in a few seconds. The execution stays paused, billing nothing for compute, until the agent calls SendTaskSuccess with that token to resume it.
// Start the agent, pass the task token, and return without waiting
const response = await agentcore.send(
new InvokeAgentRuntimeCommand({
agentRuntimeArn: AGENT_RUNTIME_ARN,
payload: new TextEncoder().encode(JSON.stringify({ ...payload, taskToken })),
runtimeSessionId: sessionId,
})
);
// Returning here does not complete the step. Step Functions stays paused until
// the agent calls SendTaskSuccess with this task token.
return { dispatched: true };
The corresponding state passes the token from context and sets a timeout and heartbeat as a safety net, so a silent agent fails the execution cleanly rather than leaving it paused indefinitely:
"ValidateDispatch": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke.waitForTaskToken",
"Parameters": {
"FunctionName": "${ValidateDispatcherFunctionArn}",
"Payload": {
"taskToken.$": "$$.Task.Token",
"document.$": "$.document",
"extractedText.$": "$.extract.extractedText",
"executionId.$": "$$.Execution.Id"
}
},
"TimeoutSeconds": 120,
"HeartbeatSeconds": 60,
"Next": "AgentCoreValidation"
}
Cost. A Lambda function runs, but only long enough to start the agent and return: a few seconds, regardless of how long the agent then takes. You pay for that brief dispatch, not for the wait, because the function has already shut down while the agent works. The wait is held by the paused Step Functions execution, which doesn’t bill for idle compute. This is the key difference from the blocking version, where the function’s billed time tracks the agent’s processing time.
Pattern 2: Direct service integration
When you don’t need custom code around the agent call, you can remove the dispatcher function and take Lambda out of the path entirely. Step Functions can call Amazon Bedrock AgentCore directly through its AWS SDK service integration, so the agent’s response flows straight into the next state. The Validate branch then becomes a single Task state:
"ValidateDirect": {
"Type": "Task",
"Resource": "arn:aws:states:::aws-sdk:bedrockagentcore:invokeAgentRuntime",
"Parameters": {
"AgentRuntimeArn": "${AgentRuntimeArn}",
"RuntimeSessionId.$": "States.Hash($$.Execution.Id, 'SHA-256')",
"Payload.$": "States.JsonToString($.prep.agentInput)"
},
"ResultSelector": { "raw.$": "$.Response" },
"TimeoutSeconds": 120,
"Next": "ParseVerdict"
}
Cost. There’s no Lambda function in the path, so there is no idle Lambda compute to pay for. Step Functions holds the wait, and a Standard workflow bills per state transition rather than for the duration of the wait, so the meaningful cost during processing is the agent itself.
Pattern 3: Lambda durable function
If you would rather express the orchestration as code in one place instead of a state machine, a Lambda durable function gives you the same cost behavior. With the @aws/durable-execution-sdk-js SDK, the pipeline stages become context.step calls, the parallel work becomes context.parallel, and the wait for the agent becomes context.waitForCallback. During that wait the function suspends and is not billed for compute. The agent resumes it with SendDurableExecutionCallbackSuccess.
// Suspend the function until the agent calls back
const result = await ctx.waitForCallback(
"validate-agentcore",
async (callbackId) =>
dispatchAgentCore(callbackId, document, extractedText, executionId),
{ timeout: { seconds: 120 } }
);
Cost. A single function holds the whole pipeline, but it doesn’t bill for compute while it is suspended waiting for the agent. You pay for the short bursts of execution between suspensions, the same economics as the task-token pattern, rather than for the wait.
Measuring the difference
The point isn’t any particular number. The agent’s runtime varies with the prompt, the model, and the document. What matters is the relationship between two values in the task-token pattern: how long the Validate state was active, versus how long the dispatcher function was actually billed. A single run from our testing makes the relationship visible:
Step Functions, ValidateDispatch state
Returned (TaskSubmitted): 14:08:19 <- the function returned and shut down
Resumed (TaskSucceeded): 14:08:34 <- the agent woke the execution
State active ........ 19.6s
Lambda, dispatcher function (CloudWatch REPORT)
Billed duration ..... 4.8s
Result: the state was active for 19.6s, but the function was billed for 4.8s.
The ~14.8s in between is wait time with no Lambda function running.
You can see the same thing in the Step Functions event history: with the task-token pattern, a TaskSubmitted event (the function returned) is separated from TaskSucceeded (the agent resumed the flow) by the agent’s processing time. A synchronous invocation has no such gap.
The following table summarizes how the Validate branch (highlighted in the preceding diagram) is implemented in each pattern:
| Blocking (anti-pattern) | Pattern 1: Task token | Pattern 2: Direct integration | Pattern 3: Durable function
---|---|---|---|---
Orchestrator | Step Functions | Step Functions | Step Functions | Lambda (code)
Lambda function in the path | yes, alive and billed | yes, but it returns early | none | the durable function (suspends)
Idle Lambda compute during the wait | pays the full wait | none | none | none
Custom code around the agent call | yes | yes | limited (state transformations) | yes
Decouples the caller from the agent | no | yes | no | yes
Relative complexity | lowest | higher (token and callback IAM) | lowest | medium (checkpoint/replay)
One caveat on reading this: the total pipeline duration isn’t a useful comparison metric, because it’s dominated by the agent’s own reasoning time, which varies from run to run and is essentially the same in every scenario. The meaningful difference is how much compute you pay for during that wait, captured by the table and the billed-duration reading. The dispatcher’s billed time stays flat while the agent’s runtime grows. The preceding numbers come from a single run. Treat them as an illustration of the relationship, not a benchmark, and measure your own workload.
Choosing a pattern
The following table summarizes the trade-offs to help you choose a pattern:
| Blocking (anti-pattern) | Pattern 1: Task-token | Pattern 2: Direct integration | Pattern 3: Durable function
---|---|---|---|---
Caller cost | Full agent processing time | Seconds (dispatch only) | Zero (no Lambda) | Seconds (dispatch only)
Integration effort | Low | Medium-high (IAM, heartbeat, timeout) | Low (single Task state) | Medium (checkpoint-and-replay model)
Business logic location | In Lambda (before + after) | In Lambda (before + after) | In Amazon States Language (ASL) only (intrinsic functions) | In Lambda (sequential code)
Best for | Prototypes, short agents | Custom pre/post-processing logic | Pure orchestration, no custom code | Complex async workflows in one function
Best practices
Guard against an agent that never answers. Set TimeoutSeconds on every waitForTaskToken state so the execution fails with States.Timeout instead of hanging indefinitely. If your agent sends heartbeats, also set HeartbeatSeconds for faster dead-agent detection. Catch the error and route to a failure or human-review path.
Use a stable session ID across retries. Set sessionId to a value derived from the execution context (such as the Step Functions execution name) so that retries resume the same agent session rather than starting fresh. In the task-token pattern, set it in the dispatcher. In the direct-integration pattern, set it in the Task state parameters.
Turn on AWS X-Ray. Enable Tracing: Active in your Step Functions and Lambda configurations. X-Ray shows exactly how long the agent spent thinking versus how long the caller spent waiting, confirming that your pattern actually released compute during the gap.
Size the dispatcher function for speed, not for the agent’s workload. The dispatcher only serializes a request and invokes an endpoint. 256 MB of memory and a 30-second timeout are usually sufficient. The heavy work happens on the agent side.
Costs
These patterns incur charges for Lambda compute, Step Functions state transitions, durable-function execution storage, and, common to every approach, Amazon Bedrock AgentCore runtime and Amazon Bedrock model inference. The agent and model cost is the same in all four cases. The architecture changes only the orchestration overhead and, in the blocking anti-pattern, the wasted idle Lambda compute. For current pricing, see the pricing pages for each service.
Conclusion
Putting an AI agent in a pipeline is the straightforward part. Calling it economically is what separates a prototype from a production design. A Lambda function that blocks on the agent is straightforward to implement but quietly expensive, spending most of its billed time waiting. In this post, we showed three ways to avoid that: a task-token callback when you need a Lambda function in the path, a direct service integration for the simplest case, and a durable function when you prefer orchestration as code. All three are driven by the same Amazon Bedrock AgentCore agent, which adapts its response to however it is called.
The full sample, including the complete agent, the state machine definitions, and the durable function, is available in the sample-bedrock-agentcore-async-stepfunctions repository on GitHub. To go deeper, review the Amazon Bedrock AgentCore documentation on asynchronous task processing.
For production deployments, use Amazon Bedrock Guardrails to enforce responsible-AI controls on agent inputs and outputs.
About the authors
Daniel Abib
Daniel is a senior specialist solutions architect at AWS, focused on generative AI and Amazon Bedrock — and passionate about serverless. He helps startups and enterprises build AI-powered applications and modernize with cloud-native architectures. A three-time Ironman finisher and four-time re:Invent speaker, he brings the same endurance mindset to building cloud solutions.
Alexandre Farber
Alexandre is a senior manager at AWS, helping LATAM’s most promising startups grow by architecting compute, data, and AI workloads. A former startup CTO who built tech and data teams from zero to thirty, he brings hands-on experience across e-commerce, fintech, and payment platforms to every customer engagement.