From dx-automation
Create all AWS resources for AI automation agents — DynamoDB tables, SQS queue, S3 bucket, SNS topic, IAM role, Lambda functions, and API Gateway. Reads config from .ai/automation/infra.json. Idempotent — skips already-existing resources.
npx claudepluginhub easingthemes/dx-aem-flow --plugin dx-automationThis skill uses the workspace's default tool permissions.
You create all AWS resources required by the AI automation agents. Everything is audit-logged via `audit.sh`. All resource names and config come from `.ai/automation/infra.json`.
Provides AWS CloudFormation templates and workflows for Lambda functions, layers, API Gateway integration, event sources, cold start optimization, monitoring, validation, and deployment. Use for Lambda infrastructure on AWS.
Builds, manages, operates Amazon API Gateway REST, HTTP, WebSocket APIs. Troubleshoots errors, configures authorizers/domains/throttling/CORS, provides SAM/CloudFormation IaC templates.
Deploys and manages AWS Bedrock AgentCore services including Gateway for REST-to-MCP APIs, Runtime for agents, Memory, Identity, Code Interpreter, Browser, and Observability using AWS CLI tools.
Share bugs, ideas, or general feedback.
You create all AWS resources required by the AI automation agents. Everything is audit-logged via audit.sh. All resource names and config come from .ai/automation/infra.json.
Read .ai/automation/infra.json. Check automationProfile:
consumer (or legacy pr-only/pr-delegation): "This repo uses the consumer profile — AWS resources are managed by the hub project. Do NOT provision AWS resources from this repo." STOP.source .ai/lib/audit.sh
export AUDIT_LOG_PREFIX=infra
Extract and display:
region — AWS regionstorage.dynamodb.dedupe.tableName — DynamoDB dedupe table namestorage.dynamodb.rateLimits.tableNamestorage.dynamodb.tokenBudget.tableNamestorage.sqs.dlq.queueNamestorage.s3.bundles.bucketNamemonitoring.snsTopic.namelambdas.wi-router.functionName, lambdas.pr-router.functionNameapiGateway.nameConfirm: "Ready to create these resources in <region>. Proceed?"
Create 3 tables (skip if already exists — catch ResourceInUseException):
# Dedupe table (TTL-enabled)
aws_resource "dynamodb/<dedupe-table>" \
aws dynamodb create-table \
--table-name "<prefix>-dedupe" \
--attribute-definitions AttributeName=eventId,AttributeType=S \
--key-schema AttributeName=eventId,KeyType=HASH \
--billing-mode PAY_PER_REQUEST \
--region "$REGION"
aws_resource "dynamodb/<dedupe-table>/ttl" \
aws dynamodb update-time-to-live \
--table-name "<prefix>-dedupe" \
--time-to-live-specification Enabled=true,AttributeName=ttl \
--region "$REGION"
# Rate limits table (pk + sk composite key)
aws_resource "dynamodb/<rate-limits-table>" \
aws dynamodb create-table \
--table-name "<prefix>-rate-limits" \
--attribute-definitions AttributeName=pk,AttributeType=S AttributeName=sk,AttributeType=S \
--key-schema AttributeName=pk,KeyType=HASH AttributeName=sk,KeyType=RANGE \
--billing-mode PAY_PER_REQUEST \
--region "$REGION"
# Token budget table (pk + sk composite key)
aws_resource "dynamodb/<token-budget-table>" \
aws dynamodb create-table \
--table-name "<prefix>-token-budget" \
--attribute-definitions AttributeName=pk,AttributeType=S AttributeName=sk,AttributeType=S \
--key-schema AttributeName=pk,KeyType=HASH AttributeName=sk,KeyType=RANGE \
--billing-mode PAY_PER_REQUEST \
--region "$REGION"
Report: 3 tables created (or already existed).
DLQ_URL=$(aws_resource "sqs/<prefix>-dlq" \
aws sqs create-queue \
--queue-name "<prefix>-dlq" \
--attributes MessageRetentionPeriod=1209600 \
--region "$REGION" \
--query 'QueueUrl' --output text)
Update infra.json: set storage.sqs.dlq.queueUrl to the returned URL.
BUCKET="<prefix>-bundles-<account-id>"
aws_resource "s3/$BUCKET" \
aws s3 mb s3://$BUCKET --region "$REGION"
# 90-day lifecycle
aws_resource "s3/$BUCKET/lifecycle" \
aws s3api put-bucket-lifecycle-configuration \
--bucket "$BUCKET" \
--lifecycle-configuration '{
"Rules": [{
"ID": "delete-old-bundles",
"Status": "Enabled",
"Expiration": {"Days": 90},
"Filter": {"Prefix": ""}
}]
}'
SNS_ARN=$(aws_resource "sns/<prefix>-alerts" \
aws sns create-topic \
--name "<prefix>-alerts" \
--region "$REGION" \
--query 'TopicArn' --output text)
Update infra.json: set monitoring.snsTopic.arn.
Create role trust policy inline, then create role and attach inline policy:
# Trust policy — Lambda service
TRUST_POLICY='{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"Service": "lambda.amazonaws.com"},
"Action": "sts:AssumeRole"
}]
}'
ROLE_ARN=$(aws_resource "iam/<prefix>-lambda-role" \
aws iam create-role \
--role-name "<prefix>-lambda-role" \
--assume-role-policy-document "$TRUST_POLICY" \
--query 'Role.Arn' --output text)
# Attach AWS managed basic Lambda execution policy
aws_resource "iam/<prefix>-lambda-role/policy/AWSLambdaBasicExecutionRole" \
aws iam attach-role-policy \
--role-name "<prefix>-lambda-role" \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
# Inline policy — DynamoDB + SQS + S3 access
aws_iam_put_role_policy "<prefix>-lambda-role" "<prefix>-storage-access" \
--policy-document "{
\"Version\": \"2012-10-17\",
\"Statement\": [
{
\"Effect\": \"Allow\",
\"Action\": [\"dynamodb:GetItem\",\"dynamodb:PutItem\",\"dynamodb:UpdateItem\",\"dynamodb:Query\",\"dynamodb:Scan\"],
\"Resource\": [
\"arn:aws:dynamodb:$REGION:$ACCOUNT_ID:table/<prefix>-dedupe\",
\"arn:aws:dynamodb:$REGION:$ACCOUNT_ID:table/<prefix>-rate-limits\",
\"arn:aws:dynamodb:$REGION:$ACCOUNT_ID:table/<prefix>-token-budget\"
]
},
{
\"Effect\": \"Allow\",
\"Action\": \"sqs:SendMessage\",
\"Resource\": \"arn:aws:sqs:$REGION:$ACCOUNT_ID:<prefix>-dlq\"
},
{
\"Effect\": \"Allow\",
\"Action\": \"s3:PutObject\",
\"Resource\": \"arn:aws:s3:::<prefix>-bundles-$ACCOUNT_ID/*\"
}
]
}"
Update infra.json: set storage.iamPolicy.attachedTo to the role name.
Create 2 placeholder Lambda functions (real code deployed by auto-deploy):
# Create minimal placeholder zip
echo 'exports.handler = async () => ({ statusCode: 200 })' > /tmp/placeholder.js
zip -j /tmp/placeholder.zip /tmp/placeholder.js
# WI Router — handles all work-item routes (dor, dod, bugfix, qa, devagent, docagent)
aws_resource "lambda/<PREFIX>-WI-Router" \
aws lambda create-function \
--function-name "<PREFIX>-WI-Router" \
--runtime nodejs20.x \
--handler wi-router.handler \
--role "$ROLE_ARN" \
--zip-file fileb:///tmp/placeholder.zip \
--timeout 30 \
--memory-size 256 \
--region "$REGION"
# PR Router — handles PR routes (pr-answer)
aws_resource "lambda/<PREFIX>-PR-Router" \
aws lambda create-function \
--function-name "<PREFIX>-PR-Router" \
--runtime nodejs20.x \
--handler pr-router.handler \
--role "$ROLE_ARN" \
--zip-file fileb:///tmp/placeholder.zip \
--timeout 30 \
--memory-size 256 \
--region "$REGION"
rm /tmp/placeholder.js /tmp/placeholder.zip
API_ID=$(aws_resource "apigateway/<PREFIX>-Agent" \
aws apigatewayv2 create-api \
--name "<PREFIX>-Agent" \
--protocol-type HTTP \
--region "$REGION" \
--query 'ApiId' --output text)
# Lambda ARNs — 2 router functions
WI_ROUTER_ARN="arn:aws:lambda:$REGION:$ACCOUNT_ID:function:<PREFIX>-WI-Router"
PR_ROUTER_ARN="arn:aws:lambda:$REGION:$ACCOUNT_ID:function:<PREFIX>-PR-Router"
# Integration: WI Router (serves all work-item routes)
WI_INT_ID=$(aws_resource "apigateway/$API_ID/integration/wi-router" \
aws apigatewayv2 create-integration \
--api-id "$API_ID" \
--integration-type AWS_PROXY \
--integration-uri "$WI_ROUTER_ARN" \
--payload-format-version 2.0 \
--region "$REGION" \
--query 'IntegrationId' --output text)
# Integration: PR Router (serves PR routes)
PR_INT_ID=$(aws_resource "apigateway/$API_ID/integration/pr-router" \
aws apigatewayv2 create-integration \
--api-id "$API_ID" \
--integration-type AWS_PROXY \
--integration-uri "$PR_ROUTER_ARN" \
--payload-format-version 2.0 \
--region "$REGION" \
--query 'IntegrationId' --output text)
# WI route — single route for all work item webhooks (tag-based routing in Lambda)
aws_resource "apigateway/$API_ID/route/wi" \
aws apigatewayv2 create-route \
--api-id "$API_ID" \
--route-key "POST /wi" \
--target "integrations/$WI_INT_ID" \
--region "$REGION"
# PR route points to PR Router
aws_resource "apigateway/$API_ID/route/pr-answer" \
aws apigatewayv2 create-route \
--api-id "$API_ID" \
--route-key "POST /pr-answer" \
--target "integrations/$PR_INT_ID" \
--region "$REGION"
# Deploy stage
aws_resource "apigateway/$API_ID/stage/prod" \
aws apigatewayv2 create-stage \
--api-id "$API_ID" \
--stage-name prod \
--auto-deploy \
--region "$REGION"
# Grant Lambda invoke permission to API Gateway
for FUNC_NAME in "<PREFIX>-WI-Router" "<PREFIX>-PR-Router"; do
aws_resource "lambda/$FUNC_NAME/permission/apigateway" \
aws lambda add-permission \
--function-name "$FUNC_NAME" \
--statement-id "apigateway-invoke" \
--action lambda:InvokeFunction \
--principal apigateway.amazonaws.com \
--source-arn "arn:aws:execute-api:$REGION:$ACCOUNT_ID:$API_ID/*" \
--region "$REGION"
done
Update infra.json:
apiGateway.id → $API_IDapiGateway.url → https://$API_ID.execute-api.$REGION.amazonaws.com/prodwebhooks.wi-userstory.url → https://$API_ID.execute-api.$REGION.amazonaws.com/prod/wiwebhooks.wi-bug.url → https://$API_ID.execute-api.$REGION.amazonaws.com/prod/wiwebhooks.pr-answer.url → https://$API_ID.execute-api.$REGION.amazonaws.com/prod/pr-answer## AWS Resources Provisioned
**Region:** <region>
| Resource | Name | Status |
|----------|------|--------|
| DynamoDB | <prefix>-dedupe | ✓ created |
| DynamoDB | <prefix>-rate-limits | ✓ created |
| DynamoDB | <prefix>-token-budget | ✓ created |
| SQS | <prefix>-dlq | ✓ created |
| S3 | <prefix>-bundles-<account> | ✓ created |
| SNS | <prefix>-alerts | ✓ created |
| IAM role | <prefix>-lambda-role | ✓ created |
| Lambda | <PREFIX>-WI-Router | ✓ created (placeholder) |
| Lambda | <PREFIX>-PR-Router | ✓ created (placeholder) |
| API Gateway | <PREFIX>-Agent | ✓ created |
**infra.json** updated with all IDs and ARNs.
**Audit log:** `.ai/logs/infra.<week>.jsonl`
### Next step
`/auto-pipelines` — Import ADO pipelines and set variables
infra.json updated with ARNs and resource identifiers for all created resources/auto-provision — Reads infra.json for resource prefix and region. Creates DynamoDB tables (dedupe + rate-limit), SQS dead-letter queue, S3 bucket for artifacts, SNS topic for alerts, IAM role with all required policies, 2 Lambda functions (placeholder code), and API Gateway with /wi and /pr routes. Updates infra.json with all ARNs and IDs.
/auto-provision (partial re-run, some resources exist) — DynamoDB tables and SQS queue already exist (ResourceInUseException). Logs "already exists" for each and continues. Creates the missing Lambda functions and API Gateway. Updates infra.json with new resource identifiers.
/auto-provision (different region) — User configured us-west-2 in infra.json instead of the default eu-west-1. All resources are created in the specified region. API Gateway endpoint URL reflects the region. IAM role policies are region-aware.
"Access Denied" or "not authorized to perform" errors
Cause: The AWS credentials don't have sufficient permissions to create the required resources.
Fix: Ensure the IAM user/role has permissions for DynamoDB, SQS, S3, SNS, IAM, Lambda, and API Gateway. Check aws sts get-caller-identity to verify you're using the right account.
"ResourceInUseException" for every resource
Cause: All resources were already provisioned (e.g., re-running after a successful provision).
Fix: This is expected — the skill is idempotent. It logs "already exists" and continues. Check infra.json to confirm all ARNs are recorded.
Lambda created but with placeholder code
Cause: /auto-provision creates Lambda functions with placeholder handlers. Actual agent code is deployed separately.
Fix: Run /auto-deploy next to upload the real Lambda code. This is the expected flow: provision first, deploy code second.
aws mutating commandsaws sts get-caller-identity --query Account --output text