01 The big picture
Five Terraform modules stack into a single request path: a static frontend, an authenticated API, a mediator Lambda, the AgentCore harness + gateway (the AI brain), a SQL-executor Lambda (the hands), and an RDS database inside a VPC.
flowchart TB user([User browser]) cf["CloudFront + S3
static chat UI"] cog["Cognito
User Pool + App Client"] api["API Gateway (HTTP)
/chat + JWT authorizer"] med["Mediator Lambda
HTTP ⇄ AgentCore streaming"] harness["AgentCore Harness
model + system prompt + tools"] gw["AgentCore Gateway
MCP tool hub"] kpi["KPI Tools Lambda
SQL query executor"] rds[("RDS PostgreSQL
company database")] user --> cf --> api cog -.->|"issues / validates JWT"| api api --> med --> harness harness --> gw --> kpi --> rds classDef edge fill:#22303f,stroke:#4493f8,color:#e6edf3; classDef ai fill:#3a2d12,stroke:#ff9900,color:#e6edf3; classDef data fill:#12351f,stroke:#3fb950,color:#e6edf3; class cf,cog,api,med edge; class harness,gw ai; class kpi,rds data;
02 What happens on a single question
The interesting part is the agent loop: the model decides which tool to call, AgentCore invokes it through the Gateway, and the result flows back to the model to be turned into a natural-language answer.
sequenceDiagram
actor U as User
participant FE as CloudFront UI
participant CG as Cognito
participant API as API Gateway
participant MED as Mediator Lambda
participant H as AgentCore Harness
participant M as Foundation model
participant GW as AgentCore Gateway
participant L as KPI Lambda
participant DB as RDS Postgres
U->>FE: Log in
FE->>CG: Authenticate
CG-->>FE: JWT token
U->>FE: "What is total revenue for July 2026?"
FE->>API: POST /chat (JWT)
API->>API: Validate JWT (Cognito authorizer)
API->>MED: {message, sessionId}
MED->>H: InvokeHarness (Converse format)
H->>M: system prompt + question
M-->>H: tool call: getMonthlyRevenue(month=2026-07)
H->>GW: route tool call
GW->>L: invoke with tool name + params
L->>DB: parameterized SQL
DB-->>L: result rows
L-->>GW: result
GW-->>H: result
H->>M: tool result
M-->>H: "Total revenue for July 2026 is 4,822.50..."
H-->>MED: streamed tokens
MED-->>API: {reply, sessionId}
API-->>FE: {reply, sessionId}
FE-->>U: Answer
03 Module wiring & trust relationships
The root Terraform composes five modules, threading each module's outputs into the next (database IP → KPI Lambda → harness → API → frontend). Two IAM roles establish the trust chain that lets the AI layer reach the tool Lambda.
flowchart TB
subgraph root["Root Terraform — module wiring"]
db["module.database
VPC · subnets · RDS · Secrets Manager · seeder"]
kpi["module.kpi_tools_lambda
SQL executor + SG"]
ac["module.agentcore_harness
Gateway + Harness + IAM"]
apim["module.api_mediator
Cognito · API GW · mediator Lambda"]
fe["module.frontend
S3 + CloudFront"]
db -->|"db host / creds"| kpi
kpi -->|"lambda ARN + name"| ac
ac -->|"harness ARN"| apim
apim -->|"api endpoint + client id"| fe
end
subgraph iam["Trust chain"]
gwrole["IAM role: Gateway
→ invoke KPI Lambda"]
hrole["IAM role: Harness
→ call Bedrock model + Gateway"]
end
ac -.defines.-> gwrole
ac -.defines.-> hrole
gwrole -.allows.-> kpi
classDef m fill:#1c2330,stroke:#ff9900,color:#e6edf3;
classDef r fill:#22303f,stroke:#4493f8,color:#e6edf3;
class db,kpi,ac,apim,fe m;
class gwrole,hrole r;
The six tools the agent can call
| Tool | Answers |
|---|---|
getMonthlyRevenue(month) | Total sales for a month |
getMonthlyProfit(month) | Revenue minus costs |
getOverdueInvoices(customerId?) | Unpaid invoices past due |
getTopProducts(month, limit?) | Best sellers by revenue |
getCustomerOrderHistory(customerId) | All orders for a customer |
getSalesByRegion(month) | Geographic sales breakdown |
04 Why AgentCore over Bedrock Agents Classic
This project uses the newer AgentCore primitives instead of classic Bedrock Agents. The practical differences show up in how tools are declared and how changes ship.
| Bedrock Agents Classic | AgentCore equivalent |
|---|---|
| Action Group (OpenAPI schema + Lambda) | Gateway Target (inline tool schema + Lambda) |
| Agent Alias | Harness Endpoint (DEFAULT) |
InvokeAgent API | InvokeHarness API |
| Agent preparation step | Changes apply immediately |
| Built-in orchestration only | Same loop, extensible + MCP-standard tools |
05 Design decisions & tradeoffs
/chat request carries a Cognito JWT validated by
the API Gateway JWT authorizer before the Mediator Lambda is invoked. The RDS instance is not publicly
accessible (publicly_accessible = false) and is reached only from inside the VPC. A
randomly generated DB password is also stored in Secrets Manager, though in this demo the Lambdas read
their DB credentials from environment variables rather than fetching the secret at runtime — a
pragmatic simplification worth hardening for production.
Running cost for a light demo is roughly $15/month, dominated by the
db.t4g.micro RDS instance; model tokens on Nova 2 Lite are about $0.001 per query.