This hands-on lab builds a real serverless order-processing agent for Northwind Traders. You will wire an EventBridge rule to a Lambda function that calls a Bedrock AgentCore Runtime agent to validate and enrich each order, then hand the result to a Step Functions state machine that retries transient failures and catches permanent ones. By the end you will have a complete serverless, event-driven agentic pipeline — no servers to patch, no queues to babysit.
What You’ll Build: A Serverless Order-Validation Pipeline
By the end, Northwind Traders will have a working serverless agentic pipeline: an EventBridge rule watches for order.created events, a Lambda function calls a Bedrock AgentCore agent to validate the order (checking stock thresholds, flagging suspicious quantities, tagging a shipping region), and a Step Functions workflow orchestrates it with retries and a dead-letter fallback. The architecture mirrors what you’d ship for real: no long-running servers, pay-per-invocation pricing, and clear observability at every hop.
This is a companion lab to the tutorial AWS Serverless Agentic AI: Lambda, Bedrock, and AgentCore — read that first if the concepts (AgentCore Runtime, event-driven Lambda, Step Functions error handling) are new to you. This lab assumes you already understand those services and walks through deploying them together.
Prerequisites, all free-tier friendly:
- An AWS account with console and CLI access, and the AWS CLI v2 configured (
aws configure). - Python 3.12 and
boto3installed locally for writing and testing the Lambda handler before packaging it. - Model access granted in the Bedrock console for at least one Anthropic Claude model in your target region (Bedrock model access is opt-in per account and region).
- An existing Bedrock AgentCore agent, or the willingness to deploy a minimal one using the
bedrock-agentcore-starter-toolkit— this lab treats the agent as a black box with a knownagentRuntimeArn, since building the agent itself is covered in the parent tutorial. - IAM permissions to create Lambda functions, EventBridge rules, Step Functions state machines, and IAM roles in your account.
- Optional but recommended: AWS SAM CLI, so you can deploy the whole stack with one command instead of clicking through the console.
Building the Serverless Order-Processing Pipeline
Northwind Traders sells outdoor gear online. Their checkout service already publishes an order.created event on purchase, which today just triggers an email receipt. In this lab you extend that pipeline so every order is also validated and enriched by an AI agent as part of a serverless design — catching obvious problems, like an order for 500 units of an item that normally sells in single digits, before a human ever looks at it.
The architecture has four moving parts. An EventBridge custom bus receives the raw event from checkout. A rule starts a Step Functions execution, passing the event’s detail object as input. The task step invokes a Lambda function, which calls a Bedrock AgentCore agent and waits for its verdict, then finishes with an enriched order or routes it to a dead-letter queue. Nothing runs unless an order arrives.
Each of the six steps below builds one piece of that chain and tests it before moving on, so when something doesn’t work you know which of the four services is responsible.
Step 1: Designing the EventBridge Rule for order.created Events
Rather than using the default event bus, Northwind Traders publishes checkout events to a dedicated custom bus named northwind-orders. Custom buses keep application events separate from AWS service events and make IAM scoping simpler — the checkout service can be granted events:PutEvents on exactly one bus ARN. Create the bus first:
aws events create-event-bus --name northwind-orders
Every event on this bus that represents a new order looks like this — the checkout service is responsible for emitting it in this shape:
{
"Source": "northwind.checkout",
"DetailType": "order.created",
"EventBusName": "northwind-orders",
"Detail": {
"orderId": "ORD-48213",
"customerId": "CUST-9931",
"items": [
{ "sku": "TENT-4P-GRN", "quantity": 2, "unitPrice": 219.00 }
],
"shippingAddress": { "country": "US", "state": "CO", "postalCode": "80301" },
"createdAt": "2026-07-01T14:02:11Z"
}
}
The EventBridge event pattern below matches only order.created events from the checkout source. Keep the pattern this narrow deliberately — a pattern that matches on source alone would also catch order.cancelled or order.refunded events once the checkout service starts emitting those, silently sending the wrong payloads into your agent workflow.
{
"source": ["northwind.checkout"],
"detail-type": ["order.created"]
}
Create the rule on the custom bus, pointing at the Step Functions state machine you’ll build in Step 3 (the ARN is a placeholder until then — you’ll update the target after the state machine exists):
aws events put-rule \
--name northwind-order-created \
--event-bus-name northwind-orders \
--event-pattern '{"source":["northwind.checkout"],"detail-type":["order.created"]}' \
--state ENABLED
Note that the rule targets the state machine directly, not the Lambda function. EventBridge supports Step Functions state machines as a native target type, which means EventBridge itself handles starting the execution — you don’t need a Lambda function whose only job is “receive event, call StartExecution.” That extra hop would add cost and a failure point with nothing to show for it.
Step 2: The Lambda Handler That Invokes the Bedrock AgentCore Runtime
The state machine’s only task step invokes a Lambda function. That function’s whole job is to take the order payload, ask the Bedrock AgentCore agent to validate and enrich it, and return a structured result. The agent itself — its instructions, its inventory-checking tools, its model — is out of scope here; you deploy it once (per the parent tutorial) and get an agentRuntimeArn that this function calls.
Bedrock AgentCore Runtime is invoked through the bedrock-agentcore boto3 client’s invoke_agent_runtime operation — a distinct client and API from the older Bedrock Agents runtime (bedrock-agent-runtime, invoke_agent). AgentCore Runtime is the current, generally-available platform for hosting custom agent code as a managed, session-isolated runtime, and it’s what this lab targets throughout.
import json
import os
import uuid
import boto3
agentcore = boto3.client("bedrock-agentcore")
AGENT_RUNTIME_ARN = os.environ["AGENT_RUNTIME_ARN"]
def handler(event, context):
"""Validate and enrich a Northwind order using a Bedrock AgentCore agent.
`event` is the order detail passed through by Step Functions (the
`detail` object from the original EventBridge event).
"""
order_id = event["orderId"]
prompt = {
"task": "validate_and_enrich_order",
"order": event,
}
response = agentcore.invoke_agent_runtime(
agentRuntimeArn=AGENT_RUNTIME_ARN,
qualifier="DEFAULT",
runtimeSessionId=f"order-{order_id}-{uuid.uuid4().hex[:8]}",
contentType="application/json",
accept="application/json",
payload=json.dumps(prompt).encode("utf-8"),
)
body = response["response"].read()
result = json.loads(body)
if result.get("status") == "REJECTED":
raise ValueError(f"Order {order_id} failed agent validation: {result.get('reason')}")
return {
"orderId": order_id,
"validated": True,
"enrichedOrder": result["enrichedOrder"],
"agentTrace": result.get("traceId"),
}
Two details matter here. First, runtimeSessionId is unique per order rather than reused — reusing one across unrelated orders would let the agent’s short-term memory bleed between customers. Second, the handler deliberately raises a Python exception on rejection instead of returning an “ok, but rejected” payload — that lets Step Functions’ own Retry/Catch logic in Step 3 decide what happens next.
Package and deploy the function with a short but not-too-short timeout — AgentCore Runtime invocations that involve a foundation-model call and one or two tool round-trips commonly take several seconds, and a 3-second Lambda timeout will fail perfectly healthy invocations:
aws lambda create-function \
--function-name northwind-order-validator \
--runtime python3.12 \
--handler validate_order.handler \
--timeout 30 \
--memory-size 256 \
--role arn:aws:iam::123456789012:role/northwind-order-validator-role \
--environment "Variables={AGENT_RUNTIME_ARN=arn:aws:bedrock-agentcore:us-east-1:123456789012:runtime/northwind-order-agent-ABC123}" \
--zip-file fileb://validate_order.zip
Before wiring this function into Step Functions at all, it is worth invoking it directly with a synthetic order so you can confirm the AgentCore call itself works in isolation — a bad agentRuntimeArn, a missing model-access grant, or a typo in the IAM policy will all surface here as a clear, single-function error instead of getting lost inside a multi-state execution trace later.
aws lambda invoke \
--function-name northwind-order-validator \
--cli-binary-format raw-in-base64-out \
--payload '{"orderId":"ORD-TEST-001","customerId":"CUST-0001","items":[{"sku":"TENT-4P-GRN","quantity":2,"unitPrice":219.00}],"shippingAddress":{"country":"US","state":"CO","postalCode":"80301"},"createdAt":"2026-07-01T14:02:11Z"}' \
response.json && cat response.json
A successful response contains the enriched order and a trace ID. If instead you see an AccessDeniedException mentioning bedrock-agentcore:InvokeAgentRuntime, the Lambda execution role from Step 4 below is missing that permission — fix that before continuing, since every later step assumes this direct invocation already works.
Step 3: The State Machine — Retry, Catch, and Orchestration
Step Functions is the layer that turns “call a Lambda function” into a resilient workflow. The ASL definition below has one Task state that invokes the validator Lambda, a Retry block that handles the two failure modes you’ll actually see in production — Bedrock throttling and generic Lambda service hiccups — and a Catch block that routes anything the retries didn’t fix to a dead-letter path instead of failing silently.
{
"Comment": "Northwind order validation workflow — EventBridge triggers this on order.created",
"StartAt": "ValidateOrderWithAgent",
"States": {
"ValidateOrderWithAgent": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Parameters": {
"FunctionName": "arn:aws:lambda:us-east-1:123456789012:function:northwind-order-validator",
"Payload.$": "$.detail"
},
"ResultSelector": {
"validatedOrder.$": "$.Payload"
},
"Retry": [
{
"ErrorEquals": ["Lambda.TooManyRequestsException"],
"IntervalSeconds": 2,
"MaxAttempts": 5,
"BackoffRate": 2.0,
"JitterStrategy": "FULL"
},
{
"ErrorEquals": [
"Lambda.ServiceException",
"Lambda.AWSLambdaException",
"Lambda.SdkClientException"
],
"IntervalSeconds": 1,
"MaxAttempts": 3,
"BackoffRate": 2.0
}
],
"Catch": [
{
"ErrorEquals": ["States.ALL"],
"ResultPath": "$.error",
"Next": "SendToDeadLetterQueue"
}
],
"Next": "OrderValidated"
},
"OrderValidated": {
"Type": "Succeed"
},
"SendToDeadLetterQueue": {
"Type": "Task",
"Resource": "arn:aws:states:::sqs:sendMessage",
"Parameters": {
"QueueUrl": "https://sqs.us-east-1.amazonaws.com/123456789012/northwind-orders-dlq",
"MessageBody.$": "$"
},
"End": true
}
}
}
The two Retry blocks are ordered deliberately. Lambda.TooManyRequestsException is matched first with a longer backoff (five attempts, starting at two seconds) since throttling under load is the most common transient failure here. The second block matches broader Lambda service exceptions with a shorter retry, since those tend to be brief blips. States.ALL in Catch must be the last, only entry — a catch-all mixed with specific error names would be unreachable.
Create the state machine, wiring in an execution role that trusts states.amazonaws.com:
aws stepfunctions create-state-machine \
--name northwind-order-validation \
--definition file://state-machine.json \
--role-arn arn:aws:iam::123456789012:role/northwind-stepfunctions-role \
--type STANDARD
Standard workflows, not Express, are the right choice here — they keep a full execution history for up to 90 days, which matters when a support agent needs to look up why order ORD-48213 landed in the dead-letter queue three weeks ago. Express workflows are cheaper for very high-volume, sub-5-minute workloads without that audit-trail requirement.
Step 4: IAM Execution Role Permissions
Two roles are involved: the Lambda function’s execution role, and the Step Functions state machine’s execution role. Keep them separate and scoped to only what each principal actually calls — a shared, over-broad role is the single most common review comment on serverless pull requests, and it’s avoidable here with about fifteen lines of policy JSON.
The Lambda execution role needs basic execution permissions (for CloudWatch Logs) plus the one Bedrock AgentCore permission the handler actually calls:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:us-east-1:123456789012:*"
},
{
"Effect": "Allow",
"Action": "bedrock-agentcore:InvokeAgentRuntime",
"Resource": "arn:aws:bedrock-agentcore:us-east-1:123456789012:runtime/northwind-order-agent-ABC123"
}
]
}
The Step Functions execution role needs permission to invoke the validator Lambda function and to send messages to the dead-letter queue:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "lambda:InvokeFunction",
"Resource": "arn:aws:lambda:us-east-1:123456789012:function:northwind-order-validator"
},
{
"Effect": "Allow",
"Action": "sqs:SendMessage",
"Resource": "arn:aws:sqs:us-east-1:123456789012:northwind-orders-dlq"
}
]
}
EventBridge also needs its own role to start executions of the state machine when the rule fires — this is a third, equally narrow role, scoped to states:StartExecution on exactly this state machine’s ARN and nothing else.
Step 5: Wiring the EventBridge Target and Deploying
With the state machine’s ARN now known, point the rule created in Step 1 at it:
aws events put-targets \
--event-bus-name northwind-orders \
--rule northwind-order-created \
--targets '[{
"Id": "northwind-order-validation-sm",
"Arn": "arn:aws:states:us-east-1:123456789012:stateMachine:northwind-order-validation",
"RoleArn": "arn:aws:iam::123456789012:role/northwind-eventbridge-sfn-role"
}]'
To deploy the whole stack as one unit instead of running each CLI command by hand, a SAM template with an AWS::Serverless::StateMachine resource and an EventBridgeRule event source is the natural fit, giving you sam deploy --guided and one CloudFormation stack. The CLI commands above are explicit so you see what each resource needs first. The core of that template, with Step 4’s IAM policies inlined, looks like this:
Resources:
OrderValidatorFunction:
Type: AWS::Serverless::Function
Properties:
Handler: validate_order.handler
Runtime: python3.12
Timeout: 30
MemorySize: 256
Environment:
Variables:
AGENT_RUNTIME_ARN: !Ref AgentRuntimeArn
Policies:
- Statement:
- Effect: Allow
Action: bedrock-agentcore:InvokeAgentRuntime
Resource: !Ref AgentRuntimeArn
OrderValidationStateMachine:
Type: AWS::Serverless::StateMachine
Properties:
DefinitionUri: statemachine/state-machine.asl.json
DefinitionSubstitutions:
OrderValidatorFunctionArn: !GetAtt OrderValidatorFunction.Arn
DeadLetterQueueUrl: !Ref OrdersDeadLetterQueue
Policies:
- LambdaInvokePolicy:
FunctionName: !Ref OrderValidatorFunction
- SQSSendMessagePolicy:
QueueName: !GetAtt OrdersDeadLetterQueue.QueueName
Events:
OrderCreated:
Type: EventBridgeRule
Properties:
EventBusName: northwind-orders
Pattern:
source: ["northwind.checkout"]
detail-type: ["order.created"]
The EventBridgeRule event source on the state machine resource replaces the separate put-rule and put-targets calls from Step 1 and Step 5 entirely — SAM generates both the rule and its IAM role automatically, which is the main argument for moving to a template once you’re past this lab’s exploratory phase.
Step 6: End-to-End Test — Publish an Event and Inspect the Execution
With this serverless pipeline wired together, publish a synthetic order event directly to the custom bus using put-events, exactly as Northwind’s checkout service would:
aws events put-events --entries '[
{
"Source": "northwind.checkout",
"DetailType": "order.created",
"EventBusName": "northwind-orders",
"Detail": "{\"orderId\":\"ORD-48213\",\"customerId\":\"CUST-9931\",\"items\":[{\"sku\":\"TENT-4P-GRN\",\"quantity\":2,\"unitPrice\":219.00}],\"shippingAddress\":{\"country\":\"US\",\"state\":\"CO\",\"postalCode\":\"80301\"},\"createdAt\":\"2026-07-01T14:02:11Z\"}"
}
]'
Within a second or two, EventBridge should have started a new execution. List recent executions for the state machine to find it:
aws stepfunctions list-executions \
--state-machine-arn arn:aws:states:us-east-1:123456789012:stateMachine:northwind-order-validation \
--max-results 5
Then pull the full execution history for that specific execution ARN — this is where you actually verify the agent did its job, not just that the Lambda function returned something:
aws stepfunctions get-execution-history \
--execution-arn arn:aws:states:us-east-1:123456789012:execution:northwind-order-validation:8f2c1e40 \
--reverse-order
A healthy execution shows a TaskScheduled → TaskStarted → TaskSucceeded sequence, with the output containing the enriched order and the agent’s trace ID. To confirm the retry path also works, temporarily lower the Lambda function’s reserved concurrency to 0, re-publish the same event, and watch the history show several LambdaFunctionFailed events followed by a successful retry once you restore concurrency — direct proof the Retry block in Step 3 is doing real work.
One more check worth running before you consider the lab complete: query the Lambda function’s CloudWatch Logs directly with Logs Insights, rather than relying only on the Step Functions execution history, since the AgentCore trace ID your handler logs on every invocation is what actually lets you correlate a specific order back to a specific agent reasoning trace later.
aws logs start-query \
--log-group-name /aws/lambda/northwind-order-validator \
--start-time $(date -d '-1 hour' +%s) \
--end-time $(date +%s) \
--query-string 'fields @timestamp, @message | filter @message like /orderId/ | sort @timestamp desc | limit 20'
Reading a handful of these log lines after every deployment, not just after a failure, is a cheap habit that catches configuration drift (a stale AGENT_RUNTIME_ARN variable, for instance) before it becomes a customer-facing incident.
Common Serverless Pipeline Mistakes
Each of these mistakes shows up repeatedly in serverless agentic AI deployments once traffic patterns get unpredictable — small mismatches between what each service assumes and what you actually configured. Read through this list before you deploy, and again if your first end-to-end test doesn’t behave the way Step 6 describes.
- Lambda timeout shorter than agent invocation latency. A 3-second default timeout is common for simple functions, but an AgentCore Runtime call that involves a model round-trip (and possibly a tool call inside the agent) routinely takes 5–15 seconds. A timeout that’s too short surfaces as a generic Lambda timeout error, not an obvious “agent was slow” signal — always set at least 30 seconds for agent-invoking functions and adjust from real latency data.
- No retry/backoff for Bedrock throttling. Skipping the
Lambda.TooManyRequestsExceptionretry block (or relying only on Lambda’s built-in async retries, which don’t apply to synchronous Step Functions invocations) means the first burst of concurrent orders fails outright instead of smoothing out over a few seconds. - EventBridge event pattern too broad or too narrow. Matching on
sourcealone catches every event type the checkout service ever emits, including cancellations and refunds you never meant to route through the validation agent. Matching on an overly specificdetailfield, meanwhile, silently drops orders whose shape shifts even slightly as the checkout service evolves. - IAM role missing bedrock-agentcore:InvokeAgentRuntime. This is an easy one to miss because it’s a distinct permission from the older
bedrock:InvokeAgentused by classic Bedrock Agents — copying an IAM policy from an older Bedrock Agents project will silently fail with an access-denied error that has nothing to do with the actual bug in your code.
Going Further
For the authoritative reference on this topic, see Amazon Bedrock AgentCore documentation.
The serverless pipeline you just built handles the happy path and the obvious failure path, but a production version would add more hardening. The most direct step is treating the dead-letter queue as first-class: attach a CloudWatch alarm to the DLQ’s ApproximateNumberOfMessagesVisible metric so a spike pages someone, and consider a second workflow that periodically drains the DLQ back through validation after human review.
End-to-end tracing is the second lever. Enabling AWS X-Ray on the Lambda function and state machine stitches together a trace spanning the EventBridge trigger, Lambda invocation, AgentCore Runtime call, and eventual SQS write — turning “why did order ORD-48213 take 40 seconds” into a single timeline. AgentCore Runtime’s own Observability integration correlates against the same trace ID the handler returns, giving a continuous view into the agent’s reasoning.
Cost is the third consideration, worth modeling before this scales past a pilot. Each order touches four billed components: EventBridge’s PutEvents, the Lambda invocation, the Step Functions transition, and the AgentCore Runtime invocation, billed on token usage plus a session charge. At 50,000 orders a month, back-of-envelope math — not just the headline “serverless is cheap” assumption — decides if this is economical versus a batch job.
Finally, consider more validation rules — fraud scoring, inventory reservation, tax calculation. Rather than growing one Lambda handler into an ever-larger function, the maintainable path is giving each concern its own state, each invoking its own narrow Lambda function and AgentCore agent. This is where a multi-agent pattern — one orchestrator delegating to specialists — starts to make sense, since Step Functions already gives you the durable orchestration layer those patterns need.
Frequently Asked Questions
Here are the questions engineers ask most often when they move an agent workload to a serverless architecture.
Why use Step Functions instead of just retrying inside the Lambda function itself?
A Lambda function retrying its own logic can’t tell the difference between “the agent call failed” and “the whole invocation is about to time out,” and it can’t route a permanently failed order anywhere other than throwing an unhandled exception. Step Functions’ Retry/Catch fields separate retry policy from business logic and give you a durable execution history to inspect afterward.
Is Amazon Bedrock AgentCore the same thing as the older Bedrock Agents feature?
No. Bedrock Agents (the bedrock-agent/bedrock-agent-runtime APIs, with action groups and knowledge bases configured inside the Bedrock console) is an earlier, more opinionated way to build a managed agent. AgentCore Runtime, invoked through the separate bedrock-agentcore client’s invoke_agent_runtime operation, is the current general-availability platform for hosting agent code built with any framework — Strands, LangGraph, CrewAI, or custom code — as a secure, session-isolated, serverless runtime. This lab uses AgentCore Runtime throughout.
Why does the Lambda handler raise an exception instead of returning a rejected status?
Because Step Functions only triggers its Retry/Catch behavior in response to a task actually failing. If the Lambda function caught the rejection internally and returned a normal {"status": "REJECTED"} payload, the state machine would see a successful task and proceed as if the order had passed — silently defeating the entire validation step.
How much does a single order validation cost to run through this pipeline?
It varies with the underlying model and the length of the agent’s reasoning, but for a short validation task using a compact model, the combined EventBridge, Lambda, Step Functions, and AgentCore Runtime cost per order is typically a fraction of a cent — the specific figure depends on your Region, model, and average token count, which is why Going Further recommends measuring it against real traffic rather than assuming a number.
What’s the difference between a Standard and an Express Step Functions workflow for this use case?
Standard workflows keep a queryable execution history for up to 90 days and bill per state transition, which suits a business process like order validation where you’ll eventually need to answer “what happened to this specific order.” Express workflows are cheaper at very high volume but only retain results in CloudWatch Logs rather than a directly queryable execution — a reasonable trade for high-throughput, low-value-per-event workloads, but not the right default here.
Serverless event-driven pipelines like this one let Northwind Traders validate every order with an AI agent, no server provisioned: EventBridge routes the event, Lambda bridges to Bedrock AgentCore, and Step Functions’ Retry/Catch fields absorb the failures real traffic brings. You built a scoped event pattern, a Lambda handler, a state machine with retries and a dead-letter fallback, tight IAM, and proved the chain end-to-end. Tracing, alarms, and cost modeling take this serverless lab to production-ready.

