Case File
fictional demoNexatel AI Support Agent
Nexatel is a fictional GCC telecom company. This is a self-directed portfolio project demonstrating Solutions Engineering capability end-to-end on AWS Bedrock - not a real client engagement, and not a live production deployment for an actual telecom.
Architecture
A single Bedrock Agent with four IAM-isolated action groups, each backed by its own narrowly-scoped Lambda. Retrieval- augmented generation against real Nexatel documentation. A full production-style edge for observability and cost control.
CloudFront
Public frontend edge
API Gateway + WAF
Rate limiting · API key / usage plan
Guardrails
Content safety · topics · PII
Bedrock Agent
Single agent, orchestration
Knowledge Base
RAG over S3 Vectors
Plan Lookup
action group
Read-only: plan catalog table
Bill Inquiry
action group
Read-only: billing table
Network Status
action group
Read-only: network status table
Escalation
action group
Write-only: can create tickets, cannot read
CloudWatch
Dashboards · alarms · budget alerts
Incident Timeline
The real engineering story.
01 · INFRASTRUCTURE
Region sustains verified physical damage
The originally-selected region, UAE (me-central-1), sustained verified physical infrastructure damage from regional conflict and had its billing suspended - confirmed directly via AWS's own console banner.
02 · MIGRATION
Full migration to eu-central-1
Migrated the entire project to Frankfurt (eu-central-1) - every script, every IAM policy, every Lambda, re-verified one resource at a time.
03 · PLATFORM GATES
Two account-verification gates, outside code or architecture
Hit a Bedrock model-access gate requiring a use-case submission and identity verification, and a CloudFront anti-fraud account-verification gate. Both handled by opening AWS Support cases and documenting the blockers transparently.
04 · COMPLIANCE
Caught a false compliance claim, rewrote it honest
Mid-build, found that the compliance documentation claimed the demo satisfied UAE/GCC data-residency requirements while actually running in Germany. Rewrote the narrative: production target is UAE (me-central-1) for PDPL Article 26, the demo runs in Frankfurt because Bedrock Agents aren't yet available in me-central-1, and the architecture is region-portable by design.
Status Ledger
What's actually live versus what's pending, stated plainly. The knowledge base and the live conversational agent are pending AWS Bedrock model-access approval - everything else below is verified and running.
| Resource | Status | Note |
|---|---|---|
| Lambdas (5) | live | all five verified |
| DynamoDB tables (6) | live | all six verified |
| API Gateway + WAF | live | rate limiting, usage plan active |
| CloudFront edge | live | verified post account-check |
| CloudWatch observability | live | dashboards, alarms, budget alerts |
| Knowledge Base ingestion | pending | resource exists; final ingestion pending |
| Live conversational agent | pending | pending Bedrock model-access approval |
Demo
Dual-mode dashboard
Switch between configuring the agent against your own content and experiencing it as an end customer would.
Upload your use case
Simulated config preview
Source: Nexatel defaults
simulated- · nexatel-plan-catalog.pdf
- · nexatel-network-sla.pdf
This preview simulates how the agent would be scoped against this content - knowledge base ingestion and the four action groups would attach here once configured.
The Problem
Telecom support teams run into the same problem everywhere: queries arrive faster than engineers can navigate the systems, documentation, and judgment calls needed to resolve them.
Success Criteria
Success was defined before writing any code.
- ✓Resolve plan queries automatically without human SOP lookup
- ✓Route billing and network requests to correct action group with accurate intent classification
- ✓Demonstrate production-grade IAM isolation (each Lambda scoped to only its own tables)
- ✓Keep infrastructure cost under $60/month at demo traffic volumes
- ✓Design for UAE/me-central-1 deployment once Bedrock Agents become available there
The constraints were real:
- -Budget: effectively zero (pay-per-use only, no provisioned throughput)
- -Timeline: 21 days, solo build, full-time employment in parallel
- -Compliance: UAE PDPL data residency as production target
- -Platform: hit two simultaneous AWS account-level verification gates mid-build, both had to be cleared
- -Region: the region I started in went down mid-build - AWS confirmed physical infrastructure damage, full migration required
System Architecture
How the pieces connect.
Each action group Lambda has its own IAM role scoped to only the tables it needs - no Lambda can read or write outside its designated tables.
Design Decisions
Why it was built this way.
The choices that look obvious in hindsight never are at decision time.
IAM-Isolated Action Groups
What I chose
One IAM role per Lambda, scoped to only its required tables
What I rejected
Single shared execution role with broad DynamoDB access
If a prompt injection attack tricks the agent into calling the wrong action group, the blast radius is contained. The escalation Lambda can't read billing data. The plan lookup Lambda can't create tickets. It's least privilege applied at the agent orchestration layer.
S3 Vectors over OpenSearch Serverless
| Option | Cost | Decision |
|---|---|---|
| S3 Vectors | ~$1/mo at demo volume | Selected |
| OpenSearch Serverless | $345/mo minimum (OCU floor) | Rejected - cost |
| Pinecone | Variable, vendor lock-in | Rejected - external dependency |
OpenSearch Serverless charges for minimum OCU capacity no matter how little you use it - $345/month even when zero queries run. At demo volumes, S3 Vectors costs under $1/month. One decision, 85% cost reduction.
eu-central-1 over me-central-1
What I chose
AWS Europe (Frankfurt)
What I rejected
AWS Middle East (UAE) - the intended production region
Bedrock Agents and the models I needed aren't available in me-central-1. The Bedrock control plane doesn't respond there - no bedrock-runtime or bedrock-agent endpoints exist in the region. eu-central-1 is the nearest region with full support. The production target is still me-central-1 for PDPL Article 26 compliance, documented clearly rather than buried.
96-Hour Escape Hatch
What I chose
Deterministic EventBridge Scheduler timer that auto-releases stuck cases
What I rejected
Trusting downstream service teams to always respond within SLA
In any distributed async system, you have to design for the stuck state. Service teams have SLAs but not guarantees. Without a timeout, a single unresponsive ticket locks a customer case permanently. 96 hours is four business days before the fail-safe fires. Any customer reply while the case is locked also releases it immediately.
Honest Compliance Framing
What I chose
Documenting the data-residency exception explicitly in all compliance materials
What I rejected
Claiming PDPL Article 26 compliance for a Frankfurt deployment
eu-central-1 is Germany, not the UAE. Writing 'data does not leave GCC territory' for a Frankfurt deployment would be false. The accurate framing is also the more credible one - a technical interviewer knows what overclaimed compliance looks like. Every real system has constraints. Documenting them clearly is part of the job.
Scalability
What changes when traffic grows.
1,000 conversations / month
current demo
- -Architecture: as documented above
- -Bedrock: on-demand inference, no provisioning
- -Cost: $23-55/month
- -Bottleneck: none at this volume
10,000 conversations / month
- -Add: DynamoDB DAX for session read caching
- -Add: Lambda reserved concurrency to prevent cold start latency spikes
- -Consider: Bedrock provisioned throughput for Sonnet if token costs exceed $300/month
- -Cost: ~$200-400/month
- -Bottleneck: Knowledge Base retrieval latency starts to matter
1,000,000 conversations / month
production telecom
- -Replace: DynamoDB on-demand with provisioned capacity + auto-scaling
- -Add: ElastiCache Redis for session state (reduces DynamoDB read load 80%)
- -Add: SQS queue in front of agent-proxy Lambda for burst absorption
- -Add: Multi-region active-passive with Route 53 health checks and failover
- -Add: Bedrock provisioned throughput (dedicated model capacity)
- -Replace: Single Knowledge Base with per-tenant Knowledge Bases for multi-carrier deployment
- -Add: GuardDuty, Security Hub, AWS Organizations SCPs for enterprise governance
- -Cost: $8,000-15,000/month
- -Bottleneck: Bedrock token throughput limits become the primary constraint
Operational Ownership
How the system is monitored and recovered.
Observability
CloudWatch dashboard: Nexatel-Support-Agent Metrics published to Nexatel/SupportAgent namespace: - ConversationCount - total requests (Count) - ConversationLatencyMs - p95 target <3000ms (ms) - ConsentGranted - consent flow completions - DataDeletionRequest - PDPL deletion requests - TicketCreated - escalation rate Alarms configured: - HighErrorRate: API Gateway 5XX > 5 in 5 min -> SNS alert - HighLatency: ConversationLatencyMs p95 > 10s -> SNS alert - Budget: $50/month threshold, alerts at 80% and 100%
Incident Response
Runbook for a latency spike:
- 1.Check ConversationLatencyMs in dashboard - identify whether spike is in agent-proxy (session/consent overhead) or Bedrock (model invocation)
- 2.If Bedrock: check AWS Health Dashboard for eu-central-1 Bedrock service status
- 3.If agent-proxy: check Lambda duration metrics - cold start vs warm execution
- 4.Rollback path: Lambda has versioned deployments - previous version can be restored in <2 minutes
Deployment
Zero-downtime Lambda updates via infrastructure/05_deploy_lambdas.py - update_function_code followed by immediate smoke test invoke. If smoke test fails, previous deployment is still active. No blue/green required at current scale.
What Broke
The things that didn't work the first time.
Every real system has a failure log. Most portfolios don't show theirs.
Region Infrastructure Failure
Impact: Full project migration required mid-build
What happened
The region I started with - me-central-1 in the UAE - went down mid-build. AWS confirmed billing was suspended due to verified physical infrastructure damage.
How resolved
Migrated the whole project to eu-central-1 - every script, IAM policy, and Lambda redeployed and re-verified. Also discovered Bedrock Agents aren't available in me-central-1 anyway, so the migration was right for two reasons.
Bedrock Account Verification Gate
Impact: Blocked KB ingestion and agent creation for multiple days
What happened
Titan and Claude Sonnet 4.6 kept returning NOT_AUTHORIZED even with AdministratorAccess. The standard use-case intake form returned 'not authorized' too - the normal resolution path was blocked.
How resolved
Turned out to be an account-level KYC gate. Submitted identity verification, opened a Support case, and escalated to the Bedrock Marketplace team. Titan cleared first. Sonnet took additional escalation. Documented in CLAUDE.md.
CloudFront Account Verification
Impact: Public edge deployment blocked
What happened
create_distribution came back AccessDenied at step 11 of the deployment script. Steps 1 through 10 had already run successfully.
How resolved
An AWS Support case cleared the verification. The script was idempotent - re-run skipped the 10 already-created resources and only ran the CloudFront step.
False Compliance Documentation
Impact: Compliance narrative required full correction across all files
What happened
An earlier version of the docs claimed data was stored in 'AWS Middle East (UAE) region (eu-central-1).' eu-central-1 is Frankfurt, Germany - not the UAE.
How resolved
Ran grep across every file, rewrote the compliance narrative with an explicit data-residency exception, updated the consent message, all script docstrings, and the README. One accurate story, everywhere.
Production Economics
What this costs to run.
Estimated at 1,000 customer conversations per month.
| Service | Monthly Cost |
|---|---|
| Amazon Bedrock (Sonnet 4.6) | $15-45 |
| S3 Vectors (Knowledge Base) | <$1 |
| AWS Lambda (5 functions) | ~$0.50 |
| API Gateway | ~$3.50 |
| DynamoDB (6 tables) | ~$1-3 |
| CloudFront | ~$1 |
| CloudWatch + alarms | ~$2 |
| WAF (2 web ACLs) | ~$12 |
| KMS (CMK) | ~$1 |
| Secrets Manager | ~$0.40 |
| Total | $36-69/month |
The S3 Vectors decision reduced cost 85% vs OpenSearch Serverless ($345/month minimum). That single architecture choice keeps the entire stack within a reasonable demo budget.
At 10,000 conversations/month, Bedrock token costs dominate (~$150-450/month). Everything else remains nearly flat.
Architecture Review
The questions a Principal SA would ask.
Every architecture has tradeoffs. Here are the hard questions and honest answers.
Why Bedrock Agents instead of LangGraph?
LangGraph is framework-level orchestration - you own the runtime, the hosting, and the integration layer. Bedrock Agents is a managed AWS service with action group routing, Knowledge Base integration, and Guardrails built in. For a system that needs IAM-controlled access to DynamoDB and Secrets Manager, native AWS orchestration removes a whole category of complexity. LangGraph is the documented path forward for multi-agent scenarios.
Why REST API instead of HTTP API?
HTTP API is cheaper and faster, but it doesn't support request validation or usage plans with API key auth - both of which I needed. The API key gates every /chat request, and request validation enforces the JSON schema before anything hits Lambda. HTTP API doesn't do either natively. REST API was the right call.
Why synchronous instead of event-driven for the main conversation flow?
Chat is synchronous by nature - a customer waiting for a reply can't wait for a queue. The Bedrock Agent invocation is synchronous by design. Async is used only where it belongs: the Route 53 SQIR automation, where the system hands off to a service team and waits hours or days for resolution.
What breaks first at scale?
Bedrock token throughput limits. On-demand inference has soft limits per region, and around 50 to 100 concurrent conversations you start hitting throttling. The fix is provisioned throughput for Sonnet 4.6 - guaranteed capacity at a predictable cost. This is the documented first scaling action.
What is the RTO and RPO?
For the demo: RTO is roughly 5 minutes - Lambda cold starts plus API Gateway propagation after a redeploy. RPO is effectively zero because DynamoDB is durable and no data lives in Lambda memory between invocations. For a production telecom system: RTO target under 60 seconds means multi-AZ Lambda and DynamoDB global tables. RPO under 1 minute means point-in-time recovery enabled.
Which AWS Well-Architected pillars are strongest?
Strongest: Security - IAM isolation, KMS CMK, WAF, Secrets Manager, Guardrails, no credentials in code. And Operational Excellence - CloudWatch custom metrics, alarms, budget alerts, idempotent scripts. Weakest is Reliability at scale: single-region, no automatic failover, and no load testing to validate concurrent behavior.
How would you reduce cost another 40%?
Three levers. First, prompt compression - the system prompt is verbose and cutting it by 40% reduces input token cost proportionally. Second, response caching - common queries like 'what is the Starter plan?' could be cached in ElastiCache with a 1-hour TTL, bypassing Bedrock entirely for repeated questions. Third, switch plan lookup and network status to Claude Haiku 4.5 where reasoning depth isn't needed, and reserve Sonnet for billing disputes and escalation.
What would you build differently with six more months?
Multi-tenant architecture first. The current build is single-tenant and a real product needs per-tenant Knowledge Bases, tenant-level IAM isolation, and usage metering. Second, a feedback loop - score every agent response against whether the customer resolved their issue and feed that back into Knowledge Base refinement. Third, Arabic language support. The GCC market is bilingual and a support agent that can't handle Arabic isn't ready for production deployment.