Today you will deploy an ambient agent on AWS that wakes up when a PDF lands in S3, waits for you to approve the job, runs, stops to ask you a question, and finishes with your answer. It uses the open-source aws-samples/sample-ambient-agent project, which wires S3, Lambda, SQS, DynamoDB and Amazon Bedrock AgentCore Runtime together with CDK. Budget about an hour, most of it waiting on Docker and CloudFormation.
Prerequisites
- An AWS account and the AWS CLI configured with default Region
us-east-1(the sample’s default). - AWS CDK v2 installed, Python 3.11+, Node.js 18+.
- Docker running locally. The agent ships as a container.
- Model access to Anthropic Claude Sonnet 4.5 enabled in the Amazon Bedrock console. Skip this and your first job fails with an access error.
- A sample PDF, such as a fake invoice. Please do not use a real one.
Steps to deploy an ambient agent with a human gate
Before typing anything, it helps to know what you are about to stand up. An S3 event notification calls a Signal Processor Lambda. That function looks up which signals care about the uploaded object and writes a job row to DynamoDB. Jobs that are approved, either automatically or by a person, go onto an SQS queue. A Job Execution Lambda drains the queue and calls the agent container in AgentCore Runtime, then writes the result and the conversation back to DynamoDB. A React UI sits on top so you can see jobs, approve them and answer the agent. Nothing runs when nothing is uploaded, so the sandbox costs pocket change while it idles.
-
Clone the repo. Everything lives in three folders:
backend(CDK),frontend(React) andagent(the container).git clone https://github.com/aws-samples/sample-ambient-agent.git cd sample-ambient-agent ls # agent/ backend/ frontend/ ... -
Bootstrap and deploy the backend. This creates the eight Lambdas, the DynamoDB tables, the SQS queue and DLQ, API Gateway with Cognito, the ECR repository and the hosting bucket. Save the outputs to a file; you will want the API URL, bucket names and table names. The first deploy takes a while because CloudFormation creates a few dozen resources. Go get coffee.
cd backend pip install -r requirements.txt cdk bootstrap cdk deploy --all --outputs-file ../outputs.json cat ../outputs.json -
Build the frontend. The React app talks to API Gateway and signs in through Cognito. Follow the repo README for wiring the stack outputs into the frontend config and publishing the build to the CloudFront-backed bucket.
cd ../frontend npm install npm run build -
Check the agent config and deploy the container. Open
agent/config.yamland confirm the human input tool is enabled. This is theask_humantool that lets the agent pause. The same file sets the model ID andmax_iterations: 10, which caps how many reasoning steps one run can take. Then run the deploy script, which builds the image, pushes it to Amazon ECR and registers it with AgentCore Runtime. Copy the Agent Runtime ARN it prints. If the push fails, it is almost always Docker not running or an expired ECR login.cd ../agent grep -A4 "human_input" config.yaml # human_input: # enabled: true # type: "human_input" # name: "ask_human" ./deploy_agent.sh # ... Agent Runtime ARN: arn:aws:bedrock-agentcore:us-east-1:123456789012:runtime/...

-
Register the agent. Sign in to the web UI (CloudFront URL from the outputs), go to the Agents page and add a new agent with the runtime ARN. The platform stores it in the agents table so signals and jobs can reference it by
agentId. Confirm the runtime exists from the CLI first:aws bedrock-agentcore-control list-agent-runtimes \ --region us-east-1 \ --query "agentRuntimes[].[agentRuntimeName,status]" \ --output table -
Create an S3 signal with autoExecute off. On the Signals page, create a signal for your documents bucket with prefix
invoices/and suffix.pdf, and leave Auto Execute unchecked. That forces every triggered job to wait for you. Under the hood the UI posts this body to/signals; you can send it yourself with an ID token copied from the browser session:curl -X POST "$API_URL/signals" \ -H "Authorization: $ID_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "signalName": "Document Processor", "signalType": "s3_file_upload", "agentId": "agent-123abc", "autoExecute": false, "configuration": { "bucketName": "ambient-agent-documents", "prefix": "invoices/", "suffix": ".pdf" } }' -
Upload a file. Use the documents bucket name from
outputs.json. The S3 event notification fires the Signal Processor Lambda, which matches your signal and writes asignal_triggeredjob.aws s3 cp ./invoice-0042.pdf s3://$DOCS_BUCKET/invoices/invoice-0042.pdf aws logs tail /aws/lambda/$SIGNAL_PROCESSOR_FN --since 5m -
Review the idle job and approve it. Open the Jobs page. Your job sits in
idlestatus because autoExecute is false. Click Execute. The API enqueues it on SQS, returns immediately, and the Job Execution worker callsinvoke_agent_runtimeon AgentCore Runtime with a session ID tied to the job. If the logs show an access error here, revisit model access in the Bedrock console. Watch it go:aws logs tail /aws/lambda/$JOB_EXECUTION_FN --follow -
Answer the agent’s question. When the agent calls
ask_human(for example, “Which expense category: Hardware, Services, or Licenses?”), the job flips tointerrupted, gets arequiresActionflag and appears on the Interrupted tab. Confirm the state in DynamoDB, then click Provide Response in the UI and type your answer.aws dynamodb get-item \ --table-name "$JOBS_TABLE" \ --key '{"jobId": {"S": "<your-job-id>"}}' \ --projection-expression "jobId, #s, requiresAction" \ --expression-attribute-names '{"#s": "status"}' -
See the result. The worker re-invokes the agent on the same session with your answer folded into a continuation prompt. The job moves to
completed, and the detail view shows the whole conversation, including your reply, for audit. Run the sameget-itemto confirm:aws dynamodb get-item \ --table-name "$JOBS_TABLE" \ --key '{"jobId": {"S": "<your-job-id>"}}' \ --query "Item.status.S" # "completed"
What you built
A working ambient agent: an S3 upload becomes a signal, a signal becomes a job, a human approves it, the agent runs on AgentCore Runtime, pauses when it needs judgment, and resumes with full context. Flip Auto Execute on for the signal and the approval step disappears, while the mid-task question stays. Swap the agent’s tools in config.yaml and the same pipeline serves a completely different workflow.
The pattern is the valuable part, not the invoice demo. Any event source that can write a job row, such as a webhook or an EventBridge rule, plugs into the same queue, worker and approval flow without touching the agent.
Next experiments: set reserved concurrency on the worker, add an alarm on the DLQ, and try the scheduled job type. When you are done, tear it all down so nothing lingers:
aws bedrock-agentcore delete-agent-runtime --agent-runtime-arn <your-agent-runtime-arn>
aws s3 rm s3://$DOCS_BUCKET --recursive
aws s3 rm s3://$FRONTEND_BUCKET --recursive
cd backend && cdk destroy --all
aws ecr delete-repository --repository-name ambient-agent --force
At Enkompass (enkompass.net), this is our home turf. We build event-driven serverless systems that scale to zero and bill like it, and we take reference stacks like this one from sandbox demo to hardened production workload. If you’d rather have seasoned cloud people handle it instead of learning the hard way at 2 AM, let’s talk. Reach out through our contact page or call us directly at +1-828-436-5667 — we’ll help you turn this from a someday-project into a done-deal.
