New serverless pattern - lambda-durable-agentcore-springai-sam-java - #3264
Conversation
Human-in-the-loop AI review with AWS Lambda durable functions and Amazon Bedrock AgentCore, in Java. A Lambda durable function orchestrates a Spring AI agent hosted on AgentCore Runtime. The agent drafts a summary of a submitted document, the workflow suspends at a callback until a human approves or rejects it, then resumes and asks the agent for a final version if approved. No compute is billed while suspended, and the completed analyze step is served from its checkpoint on resume rather than re-invoking the model. Two services: Lambda and AgentCore. Workflow state - step results, the pending callback and the handler's return value - is checkpointed by the durable execution service and read back with get-durable-execution and get-durable-execution-history, so there is no table to provision. Submission and approval are both driven from the AWS CLI. The agent ships as an ARM64 container image because AgentCore hosts source code directly only for Python and Node runtimes; scripts/build-agent-image.sh builds and pushes it. The spring-ai-agentcore-runtime-starter auto-configures the POST /invocations and GET /ping endpoints AgentCore requires, so the agent is a single method annotated with @AgentCoreInvocation. Includes unit tests using the durable execution SDK's in-memory runner covering the approve, reject and timeout paths, and asserting that replay skips completed steps instead of calling the agent again. Verified end to end on AWS: the AgentCore runtime reaches READY, both agent calls succeed against Bedrock, and analyze-document records exactly one StepStarted across two InvocationCompleted events.
|
Submission issue: #3265 |
| Effect: Allow | ||
| # GetAuthorizationToken cannot be scoped to a repository. | ||
| Action: ecr:GetAuthorizationToken | ||
| Resource: '*' |
There was a problem hiding this comment.
Can you make this more restrictive than '*'
| @@ -0,0 +1,74 @@ | |||
| { | |||
| "title": "Human-in-the-loop AI review with Lambda durable functions", | |||
|
|
||
| The durable function asks the agent to draft a summary of a document, then suspends. It resumes only when a human sends a decision, and asks the agent for a final version if the review was approved. While suspended the function consumes no compute and can wait for days. | ||
|
|
||
| Learn more about this pattern at Serverless Land Patterns: << Add the live URL here >> |
There was a problem hiding this comment.
Please update the URL
| * only on the {@code POST} it submits. | ||
| */ | ||
| private static void announceReviewRequest(String callbackId, ReviewRequest request, String draft) { | ||
| System.out.printf(""" |
There was a problem hiding this comment.
Please remove System.out.printf and use logger.
|
|
||
| ```java | ||
| String draft = ctx.step("analyze-document", String.class, | ||
| stepCtx -> agent.invoke(documentId, "analyze", request.documentText(), null, null)); |
There was a problem hiding this comment.
Can you also add retry logic?
| Use the `WorkflowFunctionName` from the stack outputs. `--durable-execution-name` names the execution so you can find it again; `--invocation-type Event` starts it asynchronously so the CLI returns immediately. | ||
|
|
||
| ```bash | ||
| aws lambda invoke \ |
There was a problem hiding this comment.
The previous step takes user to orchestrator. So, add command to come out to the project root directory before executing this command.
| Run these in the same shell, since `$ARN` is reused by the commands below. | ||
|
|
||
| ```bash | ||
| ARN=$(aws lambda list-durable-executions-by-function \ |
There was a problem hiding this comment.
Got the following error:
`aws: [ERROR]: An error occurred (InvalidParameterValueException) when calling the ListDurableExecutionsByFunction operation: Cannot filter by DurableExecutionName when both FunctionName and Qualifier are provided
Additional error details:
Type: User`
| --durable-execution-name review-001 \ | ||
| --query 'DurableExecutions[0].DurableExecutionArn' --output text) | ||
|
|
||
| aws lambda get-durable-execution-history \ |
There was a problem hiding this comment.
Got the following error:
aws: [ERROR]: An error occurred (ParamValidation): Parameter validation failed: Invalid length for parameter DurableExecutionArn, value: 0, valid min length: 1
|
|
||
| ---- | ||
|
|
||
| Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. |
| * [Java 21](https://docs.aws.amazon.com/corretto/latest/corretto-21-ug/downloads-list.html) and [Apache Maven](https://maven.apache.org/install.html) 3.9 or later | ||
| * [Docker](https://docs.docker.com/get-docker/), [Finch](https://runfinch.com/), nerdctl or Podman, to build the agent container image | ||
| * An Amazon Bedrock model available in the target Region. The default is `us.anthropic.claude-sonnet-4-5-20250929-v1:0`. Check availability with `aws bedrock get-foundation-model-availability --model-id <id> --region <region>`; if it reports anything other than `AUTHORIZED`, enable it under **Model access** in the Amazon Bedrock console. | ||
|
|
There was a problem hiding this comment.
Include an architecture diagram and explain it.
Description
Human-in-the-loop AI review with AWS Lambda durable functions and Amazon Bedrock AgentCore, in Java.
A Lambda durable function orchestrates a Spring AI agent hosted on AgentCore Runtime. The agent drafts a summary of a submitted document and flags what a reviewer should verify. The workflow then suspends at a callback until a human approves or rejects, and on approval asks the agent for a final version that folds in the reviewer's comments. No compute is billed while suspended, and the completed analyze step is served from its checkpoint on resume rather than re-invoking the model.
Two AWS services: Lambda and Amazon Bedrock AgentCore.
Workflow state — step results, the pending callback, and the handler's return value — is checkpointed by the durable execution service and read back with
get-durable-executionandget-durable-execution-history, so the pattern provisions no database. Submission and approval are both driven from the AWS CLI.Why this is useful
Agentic workflows tend to need a human somewhere in the loop, and that turns out to be awkward to build: the workflow has to wait an unbounded amount of time for a person, without holding an execution open or paying for idle compute, and without re-running the expensive model call every time it wakes up. This pattern shows the whole shape end to end, so a reader can take three specific things from it:
How to pause a workflow for a person, in Java.
waitForCallbacksuspends the execution and resumes it when a decision arrives. The pattern shows the Java API for it, including details that are easy to get wrong - operation names are mandatory and must be stable across deployments,WaitForCallbackConfignests aCallbackConfigrather than taking a timeout directly, and the execution timeout has to exceed the callback timeout or the workflow expires while still waiting.How to avoid paying twice for an agent call. Because the callback resumes into a fresh invocation that replays the handler from the top, a naive implementation re-invokes the model on every resume. The pattern shows what makes replay skip completed work, and the unit tests assert it rather than just describing it.
How to run a Java agent on AgentCore, and call it from Lambda. AgentCore hosts source code directly only for Python and Node, so a Java agent has to be an ARM64 container - the pattern includes the build script and the CloudFormation for it. It also covers two things the SDK does not make obvious:
InvokeAgentRuntimeResponsehas nopayload()accessor because the body is streamed, and reusing oneruntimeSessionIdacross both calls is what lets the agent keep its earlier turn in context across the approval gate.For readers weighing modelling choices, it also takes a position on two that caused real bugs while building it: approve and reject are both callback successes carrying the verdict as data, with failure reserved for "no decision could be obtained"; and the result keeps
outcomeseparate fromdecisionso "the reviewer said no" is never confused with "nobody answered".It sits alongside
lambda-durable-bedrock-agentcore-async, which pairs durable functions with AgentCore but useswaitForCallbackas a machine-to-machine rendezvous with no human step. The two are complementary: that one shows an async agent callback, this one a synchronous agent step behind a human gate.A note on dependencies
The agent uses
spring-ai-agentcore-runtime-starter, which auto-configures thePOST /invocationsandGET /pingendpoints AgentCore requires. It is a Spring AI Community project underorg.springaicommunity, published to Maven Central and Apache-2.0 licensed - not an official Spring AI or AWS module - and it requires Spring Boot 4.1 or later. Flagging it in case that matters for inclusion. The README says the same, and documents how to write the two endpoints by hand instead if a reader would rather not take the dependency.Testing
mvn testinorchestrator/runs 4 unit tests against the durable execution SDK's in-memory runner, covering the approve, reject and timeout paths, and asserting that replay skips completed steps rather than calling the agent again. No AWS account required.READY, both agent calls succeed against Bedrock, andanalyze-documentrecords exactly oneStepStartedacross twoInvocationCompletedevents. Approve and reject paths both confirmed, including thatfinalize-documentis skipped on rejection.sam validate --lint,sam buildand the repo'sscripts/validate.jsschema validator all pass.Checklist
README.md,example-pattern.jsonandtemplate.yamlpresent, based on_pattern-model{username}-feature-{description}The ServerlessLand URL placeholder in the README is left as
<< Add the live URL here >>pending publication.