From 9f9b1c6ccef4f8bcaff7dd773e584c39884e687a Mon Sep 17 00:00:00 2001 From: Rajil Paloth Date: Fri, 13 Mar 2026 15:31:12 +0530 Subject: [PATCH] New serverless pattern - eventbridge-scheduler-ai-agent-trigger --- .../Architecture.png | Bin 0 -> 38469 bytes .../README.md | 89 +++ .../action_group.zip | Bin 0 -> 1587 bytes .../api_schema.json | 106 ++++ .../example-pattern.json | 58 ++ .../main.tf | 507 ++++++++++++++++++ .../orchestrator.zip | Bin 0 -> 1087 bytes 7 files changed, 760 insertions(+) create mode 100644 eventbridge-scheduler-ai-agent-trigger/Architecture.png create mode 100644 eventbridge-scheduler-ai-agent-trigger/README.md create mode 100644 eventbridge-scheduler-ai-agent-trigger/action_group.zip create mode 100644 eventbridge-scheduler-ai-agent-trigger/api_schema.json create mode 100644 eventbridge-scheduler-ai-agent-trigger/example-pattern.json create mode 100644 eventbridge-scheduler-ai-agent-trigger/main.tf create mode 100644 eventbridge-scheduler-ai-agent-trigger/orchestrator.zip diff --git a/eventbridge-scheduler-ai-agent-trigger/Architecture.png b/eventbridge-scheduler-ai-agent-trigger/Architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..482111a974bab98c57cc35fab77e529158bc55f0 GIT binary patch literal 38469 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 Q!a#r2l(m#96)YnD58g)uga7~l literal 0 HcmV?d00001 diff --git a/eventbridge-scheduler-ai-agent-trigger/README.md b/eventbridge-scheduler-ai-agent-trigger/README.md new file mode 100644 index 000000000..59e80944b --- /dev/null +++ b/eventbridge-scheduler-ai-agent-trigger/README.md @@ -0,0 +1,89 @@ +# Amazon EventBridge Scheduler to Amazon Bedrock AI Agent + +This pattern demonstrates how to trigger an Amazon Bedrock AI Agent on a recurring schedule using Amazon EventBridge Scheduler. An orchestrator Lambda function, invoked by the scheduler, sends a task payload to the Bedrock Agent, which processes the input, generates an execution summary, and persists the result to a DynamoDB table via an action group Lambda. + +Learn more about this pattern at Serverless Land Patterns: https://serverlessland.com/patterns/eventbridge-scheduler-ai-agent-trigger + +Important: this application uses various AWS services and there are costs associated with these services after the Free Tier usage - please see the [AWS Pricing page](https://aws.amazon.com/pricing/) for details. You are responsible for any AWS costs incurred. No warranty is implied in this example. + +## Requirements + +* [Create an AWS account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) if you do not already have one and log in. The IAM user that you use must have sufficient permissions to make necessary AWS service calls and manage AWS resources. +* [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured +* [Git Installed](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) +* [Terraform](https://www.terraform.io/downloads.html) >= 1.0 installed + +## Architecture + +![Architecture Diagram](Architecture.png) + +The pattern deploys the following resources: + +1. **Amazon EventBridge Scheduler** – Triggers the orchestrator Lambda on a recurring schedule (default: `rate(1 hour)`). +2. **Orchestrator Lambda** (Python 3.14) – Receives the scheduler event and invokes the Bedrock Agent with a task payload. +3. **Amazon Bedrock Agent** – Processes the task payload, generates an execution summary using a foundation model (default: Claude 3 Haiku), and calls the action group. +4. **Action Group Lambda** (Python 3.14) – Persists execution records to DynamoDB. +5. **Amazon DynamoDB Table** – Stores task execution records. +6. **Amazon SQS Dead-Letter Queue** – Captures failed scheduler invocations after retries are exhausted. + +## Deployment Instructions + +1. Clone the repository: + ``` + git clone https://github.com/aws-samples/serverless-patterns + ``` +1. Change directory to the pattern directory: + ``` + cd serverless-patterns/eventbridge-scheduler-ai-agent-trigger + ``` +1. Initialize Terraform: + ``` + terraform init + ``` +1. Deploy the infrastructure: + ``` + terraform apply -auto-approve + ``` + During the prompts, provide values for: + * `aws_region` – AWS region (e.g. `us-east-1`) + * `prefix` – Unique prefix for all resource names + +1. Note the outputs from the deployment. These contain the resource names and ARNs used for testing. + +## How it works + +1. EventBridge Scheduler fires on the configured schedule and invokes the orchestrator Lambda with a JSON payload containing `taskType`, `scheduleName`, and `scheduledTime`. +2. The orchestrator Lambda calls `bedrock-agent-runtime:InvokeAgent` with the payload, targeting the agent alias. +3. The Bedrock Agent parses the payload, generates an executive summary using the foundation model, and calls the `recordTaskExecution` action group. +4. The action group Lambda writes the execution record (task ID, type, scheduled time, summary, and recorded timestamp) to the DynamoDB table. +5. If the scheduler invocation fails after 3 retries, the event is sent to the SQS dead-letter queue. + +## Testing + +1. Invoke the orchestrator Lambda manually: + ``` + aws lambda invoke \ + --function-name -agent-orchestrator \ + --payload '{"taskType":"scheduled-report","scheduleName":"manual-test","scheduledTime":"2026-03-13T10:00:00Z"}' \ + --cli-binary-format raw-in-base64-out \ + output.json + ``` +2. Check the DynamoDB table for the new execution record: + ``` + aws dynamodb scan --table-name -agent-task-executions + ``` + +## Cleanup + +1. Destroy the stack: + ``` + terraform destroy -auto-approve + ``` +1. Confirm all resources have been removed: + ``` + terraform show + ``` +---- +Copyright 2026 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +SPDX-License-Identifier: MIT-0 diff --git a/eventbridge-scheduler-ai-agent-trigger/action_group.zip b/eventbridge-scheduler-ai-agent-trigger/action_group.zip new file mode 100644 index 0000000000000000000000000000000000000000..d4ad10cbc0c3cdce5afdcf1b23cadd295224b36b GIT binary patch literal 1587 zcmV-32F&?TO9KQH00008025tpTuK8Xy?+G&0Duty01p5h0AXWvX>V>{XL4_Ka4v9p zRa6N81751LXAl)QRZD+fbBK1e~7xtHQ#?Ffq;I93&MJq_`nej8@naAUel8g&Q zmR#|7oyffWB6(Uqb6Gyce7%m+b!R1bLPB3L6(x)mljVD#;P>x5Wt|QxFhPc;rn_e9 zR*&_ZV$zi`#GYB)D`jr(KRh^{PPk3|gojJe*3ewRB+o^_dbFIPPDlC6n1M(+FBj9%fcKi3LZR>(HexvC{Phk$w$HSjNEE7t`@uUkZXOx0xm*HqLf@~_}7?JKD2zVBAm~vDpO({x7shZ?R?lcRE%}X7*&<6u`JvX5q;06q; z4WMR^@dLS)#|lvz$puO-D#_BkmS^#Sd2oLa`%-`(?=LR;&FLi%w=iLaW=EM2T)^Op{+K?c{I`_U@!gf4?CIXAZS74U1yi|5EeJGW zQbJ;X#*{`k34QBc&{~lK?u@4r(Y27<>Et!iuH{3Sqh7t&O#v&@JUbtch)wKpnV8P( z7|vTdCHzYh$EFJfx-5d%1vqo$njUdn>x;5rW(~BY*$(=PUjJfPz^H zB9yNWNz?OJE72oi6ex!bzSj>GS^p#=1;m>kuq@JWMY1KoLMh)F#1(g*hBMQvll1m5sSRI&7SpVNlv>ou9>V zfJ(D6=5yy1Z#8K1JDWC-P-dfUk5E8Cf|^pN%|m^s@w_p3-puz!{sDTK2Lb4<@;Kho z{-IG-rcKkMBa-51U@LN|-kh3dbVZ#!e@>OtB!E&{aiQCpghLAhOJh@0``eSy|3M1| ztSGo2Y_xdD67E!GrYl%M3PYKk%Iqpr2ZCWcNvn-CbPTE{VGC66tQ1fVRe(Uf3~ zdzjLgptHGEGf!+gG>s>D@yLsjvCxH6+%nNB z1--YY%Rx_nqa^TiSQc8d%3{RxwJffr3(l+M^}s0&*`hn6)+E7Gx(9@2)XgRwkYQ^p z8kQOrXdqadcx3%%EAVd`tyZ7+{yz{_gP-nsU9A$TGfzWP(#Ttw?a;o|nNN?h$E$Y) zXXF!$v;R?Za_Ta3ib)@V^+_#E^c5OewRy7?>{nnUyo`riXk)^2*eFXYhFfGstJTeF zSLDzc`G8gn8`!{--s8h$TD025Edyv80;wW~s&(R6_c+S{6v1xqR6=H6Uy)OFbnc)z zJ|s{6B*o z&)&yzD>|z(;0VBqNv&icA0on^1|%FffvKo=qxug}O928N0~7!N00;mRU2R-S10ubD z1pokm5dZ)W02lxO000010001_fdBvi0AXWvX>V>{XL4_Ka4v9pRa6B41751LX?1uD l009K|0RR956aWAKP)h{{000000RRC2RR910$OQlZ003d~-Pr&D literal 0 HcmV?d00001 diff --git a/eventbridge-scheduler-ai-agent-trigger/api_schema.json b/eventbridge-scheduler-ai-agent-trigger/api_schema.json new file mode 100644 index 000000000..6eef8a289 --- /dev/null +++ b/eventbridge-scheduler-ai-agent-trigger/api_schema.json @@ -0,0 +1,106 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Scheduled Task Execution API", + "version": "1.0.0", + "description": "Actions for recording and retrieving scheduled AI agent task executions" + }, + "paths": { + "/record-task-execution": { + "post": { + "operationId": "recordTaskExecution", + "summary": "Record a scheduled task execution in the tracking database", + "description": "Persists a task execution record with task ID, type, timestamp, and an AI-generated summary to DynamoDB", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "taskId", + "taskType", + "scheduledTime", + "executionSummary" + ], + "properties": { + "taskId": { + "type": "string", + "description": "Unique identifier for this task execution — combine scheduleName and scheduledTime" + }, + "taskType": { + "type": "string", + "description": "The category of the scheduled task (e.g. scheduled-report)" + }, + "scheduledTime": { + "type": "string", + "description": "ISO 8601 UTC timestamp when the task was scheduled to run" + }, + "executionSummary": { + "type": "string", + "description": "AI-generated summary describing the task execution and its outcome" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Execution recorded successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { "type": "string" }, + "taskId": { "type": "string" }, + "recordedAt": { "type": "string" } + } + } + } + } + } + } + } + }, + "/get-last-execution": { + "get": { + "operationId": "getLastExecution", + "summary": "Get the most recent task execution for a given task type", + "description": "Retrieves the latest execution record from DynamoDB filtered by task type", + "parameters": [ + { + "name": "taskType", + "in": "query", + "required": true, + "schema": { "type": "string" }, + "description": "Task type to look up (e.g. scheduled-report)" + } + ], + "responses": { + "200": { + "description": "Last execution found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "taskId": { "type": "string" }, + "taskType": { "type": "string" }, + "scheduledTime": { "type": "string" }, + "executionSummary": { "type": "string" }, + "recordedAt": { "type": "string" } + } + } + } + } + }, + "404": { + "description": "No executions found for the given task type" + } + } + } + } + } +} \ No newline at end of file diff --git a/eventbridge-scheduler-ai-agent-trigger/example-pattern.json b/eventbridge-scheduler-ai-agent-trigger/example-pattern.json new file mode 100644 index 000000000..0278ecb49 --- /dev/null +++ b/eventbridge-scheduler-ai-agent-trigger/example-pattern.json @@ -0,0 +1,58 @@ +{ + "title": "Trigger AI Agent with Amazon EventBridge Scheduler", + "description": "Create a EventBridge scheduler which invokes a Bedrock Agent upon triggering.", + "language": "Python", + "level": "300", + "framework": "Terraform", + "introBox": { + "headline": "How it works", + "text": [ + "This pattern demonstrates how to trigger an Amazon Bedrock AI Agent on a recurring schedule using Amazon EventBridge Scheduler. An orchestrator AWS Lambda function, invoked by the scheduler, sends a task payload to the Bedrock Agent, which processes the input, generates an execution summary, and persists the result to a DynamoDB table via an action group Lambda. The pattern includes retry logic, a dead-letter queue for failed invocations, and least-privilege IAM policies scoped to the agent alias ARN." + ] + }, + "gitHub": { + "template": { + "repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/eventbridge-scheduler-ai-agent-trigger", + "templateURL": "serverless-patterns/eventbridge-scheduler-ai-agent-trigger", + "projectFolder": "eventbridge-scheduler-ai-agent-trigger", + "templateFile": "main.tf" + } + }, + "resources": { + "bullets": [ + { + "text": "Invoke a Lambda function on a schedule", + "link": "https://docs.aws.amazon.com/lambda/latest/dg/with-eventbridge-scheduler.html" + }, + { + "text": "Allow users to view information about and invoke an agent", + "link": "https://docs.aws.amazon.com/bedrock/latest/userguide/security_iam_id-based-policy-examples-agent.html#security_iam_id-based-policy-examples-perform-actions-agent" + } + ] + }, + "deploy": { + "text": [ + "terraform init", + "terraform apply" + ] + }, + "testing": { + "text": [ + "See the GitHub repo for detailed testing instructions." + ] + }, + "cleanup": { + "text": [ + "terraform destroy", + "terraform show" + ] + }, + "authors": [ + { + "name": "Rajil Paloth", + "image": "https://i.ibb.co/r2TsqGf6/Passport-size.jpg", + "bio": "ProServe Delivery Consultant at AWS", + "linkedin": "paloth" + } + ] +} diff --git a/eventbridge-scheduler-ai-agent-trigger/main.tf b/eventbridge-scheduler-ai-agent-trigger/main.tf new file mode 100644 index 000000000..d678e0fc5 --- /dev/null +++ b/eventbridge-scheduler-ai-agent-trigger/main.tf @@ -0,0 +1,507 @@ +terraform { + required_version = ">= 1.0" + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 6.32.1" + } + } +} + +provider "aws" { + region = var.aws_region +} + +############################################################ +# Variables +############################################################ + +variable "aws_region" { + description = "AWS region for resources (e.g. us-east-1, us-west-2)" + type = string + + validation { + condition = can(regex("^[a-z]{2}-[a-z]+-[0-9]+$", var.aws_region)) + error_message = "Must be a valid AWS region (e.g. us-east-1, eu-west-2)." + } +} + +variable "prefix" { + description = "Unique prefix for all resource names" + type = string + + validation { + condition = can(regex("^[a-z0-9][a-z0-9\\-]{1,20}$", var.prefix)) + error_message = "Prefix must be 2-21 lowercase alphanumeric characters or hyphens." + } +} + +variable "bedrock_model_id" { + description = "Bedrock foundation model ID for the agent" + type = string + default = "anthropic.claude-3-haiku-20240307-v1:0" +} + +variable "schedule_expression" { + description = "EventBridge Scheduler expression (e.g. rate(1 hour), cron(0 9 * * ? *))" + type = string + default = "rate(1 hour)" +} + +variable "log_retention_days" { + description = "CloudWatch log retention in days (0 = never expire)" + type = number + default = 14 +} + +############################################################ +# Data Sources +############################################################ + +data "aws_caller_identity" "current" {} +data "aws_region" "current" {} + +############################################################ +# 1. DYNAMODB TABLE (agent writes execution records here) +############################################################ + +resource "aws_dynamodb_table" "task_executions" { + name = "${var.prefix}-agent-task-executions" + billing_mode = "PAY_PER_REQUEST" + hash_key = "TaskId" + + attribute { + name = "TaskId" + type = "S" + } + + tags = { + Project = "${var.prefix}-ai-agent-scheduler" + } +} + +############################################################ +# 2. ACTION GROUP LAMBDA (Bedrock Agent calls this to +# read/write DynamoDB) +############################################################ + +resource "aws_iam_role" "action_group_role" { + name = "${var.prefix}-action-group-lambda-role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [{ + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { Service = "lambda.amazonaws.com" } + }] + }) +} + +resource "aws_iam_role_policy_attachment" "action_group_basic" { + role = aws_iam_role.action_group_role.name + policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" +} + +resource "aws_iam_role_policy" "action_group_dynamodb" { + name = "${var.prefix}-action-group-dynamodb" + role = aws_iam_role.action_group_role.id + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [{ + Effect = "Allow" + Action = [ + "dynamodb:PutItem", + "dynamodb:GetItem", + "dynamodb:UpdateItem", + "dynamodb:Query", + "dynamodb:Scan" + ] + Resource = [ + aws_dynamodb_table.task_executions.arn, + "${aws_dynamodb_table.task_executions.arn}/index/*" + ] + }] + }) +} + +resource "aws_cloudwatch_log_group" "action_group_logs" { + name = "/aws/lambda/${var.prefix}-agent-action-group" + retention_in_days = var.log_retention_days +} + +resource "aws_lambda_function" "action_group" { + function_name = "${var.prefix}-agent-action-group" + role = aws_iam_role.action_group_role.arn + handler = "action_group.lambda_handler" + runtime = "python3.14" + timeout = 30 + memory_size = 128 + filename = "action_group.zip" + + environment { + variables = { + DYNAMODB_TABLE = aws_dynamodb_table.task_executions.name + PREFIX = var.prefix + } + } + + depends_on = [ + aws_cloudwatch_log_group.action_group_logs, + aws_iam_role_policy_attachment.action_group_basic, + aws_iam_role_policy.action_group_dynamodb, + ] +} + +############################################################ +# 3. BEDROCK AGENT (AI agent with action group) +############################################################ + +# --- Agent IAM Role --- + +resource "aws_iam_role" "bedrock_agent_role" { + name = "${var.prefix}-bedrock-agent-role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [{ + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { Service = "bedrock.amazonaws.com" } + Condition = { + StringEquals = { + "aws:SourceAccount" = data.aws_caller_identity.current.account_id + } + } + }] + }) +} + +resource "aws_iam_role_policy" "bedrock_agent_model" { + name = "${var.prefix}-bedrock-invoke-model" + role = aws_iam_role.bedrock_agent_role.id + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [{ + Effect = "Allow" + Action = "bedrock:InvokeModel" + Resource = "arn:aws:bedrock:${var.aws_region}::foundation-model/${var.bedrock_model_id}" + }] + }) +} + +# --- Agent --- + +resource "aws_bedrockagent_agent" "task_agent" { + agent_name = "${var.prefix}-scheduled-task-agent" + agent_resource_role_arn = aws_iam_role.bedrock_agent_role.arn + foundation_model = var.bedrock_model_id + idle_session_ttl_in_seconds = 600 + prepare_agent = false # prepared after action group is attached + + instruction = <<-EOT + You are a Scheduled Task Execution Agent. You run on a schedule via + Amazon EventBridge Scheduler. Each time you are invoked you must: + + 1. PARSE the task payload provided to you (JSON with taskType, scheduleName, + and scheduledTime). + 2. GENERATE a brief, one-paragraph executive summary describing the task + execution — include the task type, timestamp, and a note that the + execution completed successfully. + 3. CALL the recordTaskExecution action to persist the execution record. + Use the values from the payload for taskId (use scheduleName + timestamp + combined), taskType, scheduledTime, and pass your generated summary as + the executionSummary. + 4. CONFIRM success by returning a short completion message. + + Always use the exact values from the payload. Never fabricate timestamps + or identifiers. + EOT +} + +# --- Action Group --- + +resource "aws_bedrockagent_agent_action_group" "task_actions" { + action_group_name = "${var.prefix}-task-execution-actions" + agent_id = aws_bedrockagent_agent.task_agent.agent_id + agent_version = "DRAFT" + skip_resource_in_use_check = true + + action_group_executor { + lambda = aws_lambda_function.action_group.arn + } + + api_schema { + payload = file("${path.module}/api_schema.json") + } +} + +# --- Allow Bedrock to invoke Action Group Lambda --- + +resource "aws_lambda_permission" "allow_bedrock" { + statement_id = "AllowBedrockAgentInvoke" + action = "lambda:InvokeFunction" + function_name = aws_lambda_function.action_group.function_name + principal = "bedrock.amazonaws.com" + source_arn = aws_bedrockagent_agent.task_agent.agent_arn +} + +# --- Prepare Agent (must happen after action group is attached) --- + +resource "terraform_data" "prepare_agent" { + depends_on = [ + aws_bedrockagent_agent_action_group.task_actions, + aws_lambda_permission.allow_bedrock, + ] + + triggers_replace = [ + aws_bedrockagent_agent.task_agent.agent_id, + aws_bedrockagent_agent_action_group.task_actions.action_group_name, + ] + + provisioner "local-exec" { + command = "aws bedrock-agent prepare-agent --agent-id ${aws_bedrockagent_agent.task_agent.agent_id} --region ${var.aws_region} && sleep 15" + } +} + +# --- Agent Alias (points to the prepared version) --- + +resource "aws_bedrockagent_agent_alias" "live" { + agent_alias_name = "${var.prefix}-live" + agent_id = aws_bedrockagent_agent.task_agent.agent_id + description = "Live alias for scheduled task agent" + + depends_on = [terraform_data.prepare_agent] +} + +############################################################ +# 4. ORCHESTRATOR LAMBDA (Scheduler invokes this → +# it invokes the Bedrock Agent) +############################################################ + +resource "aws_iam_role" "orchestrator_role" { + name = "${var.prefix}-orchestrator-lambda-role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [{ + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { Service = "lambda.amazonaws.com" } + }] + }) +} + +resource "aws_iam_role_policy_attachment" "orchestrator_basic" { + role = aws_iam_role.orchestrator_role.name + policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" +} + +# ── InvokeAgent requires permission on the agent ALIAS ARN ── +# ARN format: arn:aws:bedrock:{region}:{account}:agent-alias/{agent-id}/{alias-id} +# The alias resource exposes this as agent_alias_arn. +# We also grant access to all aliases of this agent so the role +# continues to work if a new alias is created later. + +resource "aws_iam_role_policy" "orchestrator_bedrock" { + name = "${var.prefix}-orchestrator-invoke-agent" + role = aws_iam_role.orchestrator_role.id + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [{ + Sid = "InvokeBedrockAgentAlias" + Effect = "Allow" + Action = "bedrock:InvokeAgent" + Resource = [ + # Specific alias created by this stack + aws_bedrockagent_agent_alias.live.agent_alias_arn, + # Wildcard for any future alias of the same agent + "arn:aws:bedrock:${var.aws_region}:${data.aws_caller_identity.current.account_id}:agent-alias/${aws_bedrockagent_agent.task_agent.agent_id}/*" + ] + }] + }) +} + +resource "aws_cloudwatch_log_group" "orchestrator_logs" { + name = "/aws/lambda/${var.prefix}-agent-orchestrator" + retention_in_days = var.log_retention_days +} + +resource "aws_lambda_function" "orchestrator" { + function_name = "${var.prefix}-agent-orchestrator" + role = aws_iam_role.orchestrator_role.arn + handler = "orchestrator.lambda_handler" + runtime = "python3.14" + timeout = 120 + memory_size = 256 + filename = "orchestrator.zip" + + environment { + variables = { + BEDROCK_AGENT_ID = aws_bedrockagent_agent.task_agent.agent_id + BEDROCK_AGENT_ALIAS_ID = aws_bedrockagent_agent_alias.live.agent_alias_id + PREFIX = var.prefix + } + } + + depends_on = [ + aws_cloudwatch_log_group.orchestrator_logs, + aws_iam_role_policy_attachment.orchestrator_basic, + aws_iam_role_policy.orchestrator_bedrock, + ] +} + +############################################################ +# 5. SQS DEAD-LETTER QUEUE (captures failed scheduler +# invocations after retries are exhausted) +############################################################ + +resource "aws_sqs_queue" "scheduler_dlq" { + name = "${var.prefix}-scheduler-dlq" + message_retention_seconds = 1209600 # 14 days +} + +############################################################ +# 6. EVENTBRIDGE SCHEDULER (triggers the orchestrator +# on a recurring schedule) +############################################################ + +resource "aws_iam_role" "scheduler_role" { + name = "${var.prefix}-scheduler-role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [{ + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { Service = "scheduler.amazonaws.com" } + }] + }) +} + +resource "aws_iam_role_policy" "scheduler_invoke_lambda" { + name = "${var.prefix}-scheduler-invoke-lambda" + role = aws_iam_role.scheduler_role.id + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Sid = "InvokeLambda" + Effect = "Allow" + Action = "lambda:InvokeFunction" + Resource = aws_lambda_function.orchestrator.arn + }, + { + Sid = "SendToDLQ" + Effect = "Allow" + Action = "sqs:SendMessage" + Resource = aws_sqs_queue.scheduler_dlq.arn + } + ] + }) +} + +resource "aws_sqs_queue_policy" "allow_scheduler" { + queue_url = aws_sqs_queue.scheduler_dlq.id + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [{ + Sid = "AllowSchedulerSendMessage" + Effect = "Allow" + Principal = { Service = "scheduler.amazonaws.com" } + Action = "sqs:SendMessage" + Resource = aws_sqs_queue.scheduler_dlq.arn + Condition = { + ArnEquals = { + "aws:SourceArn" = "arn:aws:scheduler:${var.aws_region}:${data.aws_caller_identity.current.account_id}:schedule/default/${var.prefix}-trigger-ai-agent" + } + } + }] + }) +} + +resource "aws_cloudwatch_log_group" "scheduler_logs" { + name = "/aws/scheduler/${var.prefix}-trigger-ai-agent" + retention_in_days = var.log_retention_days +} + +resource "aws_scheduler_schedule" "trigger_agent" { + name = "${var.prefix}-trigger-ai-agent" + schedule_expression = var.schedule_expression + + flexible_time_window { + mode = "OFF" + } + + target { + arn = aws_lambda_function.orchestrator.arn + role_arn = aws_iam_role.scheduler_role.arn + + input = jsonencode({ + taskType = "scheduled-report" + scheduleName = "${var.prefix}-trigger-ai-agent" + scheduledTime = "REPLACED_AT_RUNTIME" + }) + + retry_policy { + maximum_retry_attempts = 3 + maximum_event_age_in_seconds = 3600 + } + + dead_letter_config { + arn = aws_sqs_queue.scheduler_dlq.arn + } + } + + depends_on = [aws_cloudwatch_log_group.scheduler_logs] +} + +############################################################ +# 7. OUTPUTS +############################################################ + +output "prefix" { + value = var.prefix +} + +output "schedule_name" { + value = aws_scheduler_schedule.trigger_agent.name +} + +output "schedule_arn" { + value = aws_scheduler_schedule.trigger_agent.arn +} + +output "orchestrator_lambda_name" { + value = aws_lambda_function.orchestrator.function_name +} + +output "action_group_lambda_name" { + value = aws_lambda_function.action_group.function_name +} + +output "bedrock_agent_id" { + value = aws_bedrockagent_agent.task_agent.agent_id +} + +output "bedrock_agent_alias_id" { + value = aws_bedrockagent_agent_alias.live.agent_alias_id +} + +output "bedrock_agent_alias_arn" { + value = aws_bedrockagent_agent_alias.live.agent_alias_arn + description = "The alias ARN that the orchestrator Lambda is authorized to invoke" +} + +output "dynamodb_table_name" { + value = aws_dynamodb_table.task_executions.name +} + +output "dlq_queue_url" { + value = aws_sqs_queue.scheduler_dlq.url +} \ No newline at end of file diff --git a/eventbridge-scheduler-ai-agent-trigger/orchestrator.zip b/eventbridge-scheduler-ai-agent-trigger/orchestrator.zip new file mode 100644 index 0000000000000000000000000000000000000000..9fb6a973f389e54312579fa7539db9090dacb492 GIT binary patch literal 1087 zcmWIWW@Zs#U|`^2_!yHL)77@QznhtXp@xHjfuBK!A-^a&Bel4sD6u5JNUxwWG=!6Z zS#H(lOnD$Kt>9*0Wckj>zyLO&HRNvIZ8MR5--Bfe43rW#1(fWT;Q4XL^YYn@X(FBZ z+e`#9CT_dwvBWVb)AqcB{38Dw>NgctUmY(sJUHR#;rG@y_VWE2mzUaw?oVHS?cYpgQ)tD5qtVS=f76re4V#lW)jqxC@Y1s-s(%wVTRk(;XPKpZX@Q!YX=VV+%g^`M z=TE=+h}UR}=gu0zsFUtRm4Bc8d-p0rY3MbxiIZz$IqBA4Toc7O9H0LAG#Sd=fP_& znUf!1H0^w`{6WgC`~oHooxVlK`I=Xrn)Isn(bN@{>r6i1Fx$Ds?@fp~cfISm7h9L} zJvLdLq9ze08ZEH%&q=Ii?q}KW4+t@6UhF-PyhS?!QEX4h!DXwodP~ zb{{!ty87zOId6|n)#93WyGkfh#(3%K*xCN;m3^IJmz`B8bY7O0_D|{2LEZiPrdZ0w z7pQYwdGa{e*rT>5Tj8gezGmT?ebf1hpS_u%&ip3%@#B{h*L<5M{b`C(i1K0W=$l`k zY%lO;S6&|$Q*g6q&qK@H;@qXGGFH701-4Dyc}Vz!+}^sctF)~mHMhAZ=a=ybS@MK! z-F(4y$=uh;+;4hY%QWUR&b{~fp{~922haQFmdc3{Es76stb0`T_=kLeHzSiAGp<}M q0nF_T48Uy5u%r>h!pP&SkUWl-zXQBk*+9w}fiM(EUjY^q3=9C{Ao%bA literal 0 HcmV?d00001