
Building a Multi-Agent AI Travel Request Assistant on AWS with Strands Agents SDK and Amazon Bedrock
A five-agent corporate travel pipeline on AWS that automates policy compliance, budget validation, GDS booking, tiered manager approvals with auto-escalation, and employee notifications — end to end.
Building a Multi-Agent AI Travel Request Assistant on AWS with Strands Agents SDK and Amazon Bedrock
GitHub: https://github.com/its-me-sweta/Travel_Request_AI_Assistant.git
Introduction
Corporate travel management is a high-friction, multi-step process that touches policy compliance, budget controls, booking systems, multi-level approvals, and employee communication — all before a single ticket is issued.
In this post I'll walk you through how I built a production-ready, five-agent AI pipeline that automates the entire corporate travel request lifecycle using:
- Strands Agents SDK with
@tooldecorators - Amazon Bedrock Nova Pro (
us.amazon.nova-pro-v1:0) as the LLM backbone - Amazon RDS Aurora for policy, budget, and booking data
- AWS Lambda for GDS (Global Distribution System) integration
- Amazon DynamoDB for pipeline state, approvals, and audit trails
- Amazon SES + SNS for employee and manager notifications
- AgentCore Runtime for serverless deployment
- FastAPI as the REST layer
The pipeline includes a human-in-the-loop approval gate with automatic escalation — a pattern aligned with enterprise governance requirements and EU AI Act human oversight provisions.
Architecture Overview
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
┌─────────────────────────────────┐
│ FastAPI / REST Layer │
│ POST /travel/submit │
└───────────────┬─────────────────┘
│
┌───────────────▼─────────────────┐
│ Orchestrator Agent │
│ Routing · State · Escalation │
└──┬──────────────────────────┬───┘
│ │
┌────────────▼────┐ ┌───────────▼──────────┐
│ Policy Agent │ │ Budget Agent │
│ RAG + Rules │ │ RDS Aurora · Holds │
└────────────┬────┘ └───────────┬──────────┘
│ │
└──────────┬───────────────┘
│
┌─────────────▼───────────────────┐
│ Booking Agent │
│ Lambda GDS · DynamoDB │
└─────────────┬───────────────────┘
│
┌──────────────────┼──────────────────┐
│ │ │
┌──────────▼──────┐ ┌────────▼───────┐ ┌──────▼──────────┐
│ Approval Agent │ │ Auto-Approve │ │ VP Escalation │
│ SES · SNS │ │ (low-value) │ │ (>$5,000) │
└──────────┬──────┘ └────────────────┘ └─────────────────┘
│
┌──────────▼──────────────────────────────────────────┐
│ Notification Agent │
│ SES · SNS · PDF Itinerary · Calendar Invite (.ics) │
└─────────────────────────────────────────────────────┘The Five Agents
| Agent | Role | AWS Services |
|---|---|---|
| Orchestrator Agent | Routes, coordinates, and escalates | DynamoDB, SNS |
| Policy Compliance Agent | Validates against corporate travel policy | S3 (policy corpus), DynamoDB |
| Budget Validation Agent | Checks department budget availability | RDS Aurora, DynamoDB |
| Booking Agent | Searches GDS, ranks options, creates bookings | Lambda, DynamoDB |
| Approval Agent | Manager approval workflow with escalation | SES, SNS, DynamoDB |
| Notification Agent | SES emails, SNS alerts, PDF, calendar | SES, SNS, Lambda, S3 |
Why Strands Agents SDK?
Strands gives each agent a clean, Pythonic tool interface. No boilerplate JSON schema — the SDK infers parameter types from Python type hints and docstrings:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from strands import Agent, tool
from strands.models import BedrockModel
def check_advance_booking(travel_date: str, request_date: str, destination_type: str) -> dict:
"""
Checks if the request was made with sufficient advance notice.
Min 7 days domestic, 14 days international.
"""
...
agent = Agent(
model=BedrockModel(model_id="us.amazon.nova-pro-v1:0", region_name="us-east-1"),
system_prompt=POLICY_SYSTEM_PROMPT,
tools=[check_advance_booking, check_cabin_class, check_hotel_rate_cap, ...],
name="policy_compliance_agent",
)Each agent is self-contained with its own system prompt, tool list, and responsibility boundary. The Orchestrator wires them together.
Pipeline Deep Dive
Stage 1 — Policy Compliance Agent
The Policy Agent validates four dimensions against the corporate travel policy:
1. Advance Booking Check
1
2
3
4
5
6
7
8
def check_advance_booking(travel_date: str, request_date: str, destination_type: str) -> dict:
"""Min 7 days domestic, 14 days international."""
days_advance = (travel_date_parsed - request_date_parsed).days
required = 14 if destination_type == "international" else 7
if days_advance < required:
return {"check_result": "FAIL", "days_advance": days_advance, ...}
return {"check_result": "PASS", "days_advance": days_advance}2. Cabin Class Validation
1
2
3
4
5
6
CABIN_RULES = {
"IC": {"economy", "premium_economy", "business"},
"Director": {"economy", "premium_economy", "business"},
"Manager": {"economy", "premium_economy"},
"Analyst": {"economy"},
}Business class is permitted for Director+ on flights over 6 hours — a common corporate policy.
3. Hotel Rate Cap
| Destination | Cap |
|---|---|
| Domestic | $150/night |
| International | $250/night |
4. Destination Restrictions
Sanctioned countries return
BLOCKED. High-risk destinations trigger VP approval. The agent aggregates all checks into a compliance report:1
compliance_status: COMPLIANT | NON_COMPLIANT | REQUIRES_EXCEPTIONStage 2 — Budget Validation Agent
The Budget Agent integrates with RDS Aurora in real time to check department travel budgets:
1
2
3
4
5
6
7
8
9
10
11
def get_department_budget(department: str, fiscal_year: int) -> dict:
"""Queries RDS Aurora for total_budget, ytd_spend, and soft_holds."""
result = rds_data.execute_statement(
resourceArn=RDS_ARN, secretArn=SECRET_ARN, database=DATABASE,
sql="""SELECT total_budget, ytd_spend, soft_holds
FROM department_budgets WHERE department=:d AND fiscal_year=:y""",
...
)
available = total_budget - ytd_spend - soft_holds
return {"available_budget": available, ...}Cost Estimation Formula:
1
2
3
4
total_cost = flight_cost
+ (nightly_rate × nights)
+ (per_diem_meals + per_diem_incidentals) × travel_days
+ ground_transport (10% of flight if not specified)Budget Decision Logic:
| Variance | Status |
|---|---|
| Available ≥ Estimated | APPROVED |
| Over by ≤ 20% | WARN (CFO note required) |
| Over by > 20% | REJECTED |
When approved, the agent places a soft budget hold in DynamoDB — a reservation that prevents double-spending while the request awaits manager approval.
Stage 3 — Booking Agent
The Booking Agent integrates with the corporate GDS (Global Distribution System) via AWS Lambda and ranks options using a value scoring algorithm:
1
2
3
4
5
def flight_score(f):
price_score = (1 - f["price_usd"] / 5000) * 40 # 40% weight: cost
preferred = 20 if f["preferred_airline"] else 0 # 20%: negotiated rates
conn_score = (2 - f["connections"]) * 15 # 30%: direct preferred
return price_score + preferred + conn_scoreThe agent selects the top-ranked option and preserves 2–3 alternatives in the DynamoDB booking record for manager review. On approval, it submits to the GDS asynchronously via Lambda:
1
2
3
4
5
lambda_client.invoke(
FunctionName="travel-gds-booking-submit",
InvocationType="Event", # fire-and-forget
Payload=json.dumps({...}),
)Stage 4 — Human-in-the-Loop Approval Gate
The Orchestrator applies tiered approval routing:
1
2
3
4
# Auto-approve: domestic, economy, ≤$500
# Manager approval: $500–$2,000 or business class
# Manager + Finance: international travel
# VP approval: > $5,000The Approval Agent sends one-click approve/reject emails via SES with embedded action URLs:
1
2
approve_url = f"https://travel.company.com/approve/{approval_id}?action=approve"
reject_url = f"https://travel.company.com/approve/{approval_id}?action=reject"Escalation ladder:
- No response in 24h → escalate to manager's manager
- No response in 48h → escalate to VP with urgent flag
- No response in 72h → auto-approve with full audit note
If rejected, the Budget Agent's soft hold is automatically released.
Stage 5 — Notification Agent
The Notification Agent dispatches context-aware communications:
1
2
3
4
5
6
7
8
9
def create_calendar_invite(employee_email, destination, departure_date, return_date, booking_id):
"""Generates and sends an .ics calendar file via SES."""
ics_content = f"""BEGIN:VCALENDAR
...
SUMMARY:Business Travel — {destination} [{booking_id}]
DTSTART;VALUE=DATE:{departure_date.replace('-', '')}
...
END:VCALENDAR"""On approval, the agent:
- Sends a confirmation email with the full itinerary
- Generates a PDF via Lambda → S3
- Sends a
.icscalendar invite - Notifies Finance via SNS with cost centre and budget code
Deployment
1
2
3
4
5
# agentcore_handler.py (also in main.py)
def handler(event: dict, context) -> dict:
payload = event.get("travel_request", event)
result = orchestrate_travel_request(payload)
return {"statusCode": 200, "body": json.dumps(result)}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Submit a travel request
curl -X POST https://your-api/travel/submit \
-H "Content-Type: application/json" \
-d '{
"employee_id": "EMP-001",
"employee_name": "Sweta Jha",
"employee_grade": "Manager",
"origin": "BLR",
"destination": "Amsterdam",
"destination_country": "NL",
"travel_date": "2025-08-15",
"return_date": "2025-08-20",
"nights": 5,
"cabin_class": "economy",
"estimated_cost": 2400.00,
"nightly_hotel_rate": 180.00,
"purpose": "AWS Summit Amsterdam"
}'AWS Infrastructure Summary
| Resource | Purpose |
|---|---|
| Amazon Bedrock Nova Pro | LLM for all five agents |
| RDS Aurora (PostgreSQL) | Department budgets + booking records |
| DynamoDB | Pipeline state, approvals, budget holds, audit log |
| AWS Lambda | GDS flight/hotel search + PDF generation |
| Amazon SES | Employee + manager emails, calendar invites |
| Amazon SNS | Finance alerts, escalation notifications |
| Amazon S3 | Policy document corpus, PDF itineraries |
| AgentCore Runtime | Serverless agent deployment |
Key Design Patterns
Soft Budget Holds — Reserve budget as soon as a request enters the pipeline, not when it's approved. Prevents over-commitment across simultaneous requests.
Tiered Auto-Approval — Low-value, policy-compliant requests bypass human review entirely. Only genuinely high-risk requests consume manager attention.
Escalation with SLA — Every pending approval has a countdown. The system never blocks indefinitely — it escalates automatically and documents every step.
Tool-First Agent Design — Every capability is a testable
@tool function. Agents stay thin; business logic stays in tools.Idempotent Operations — Budget holds and booking records use unique IDs. Retries are safe.
Try It Yourself
1
2
3
4
git clone https://github.com/its-me-sweta/Travel_Request_AI_Assistant.git
cd travel-request-assistant
pip install -r requirements.txt
uvicorn main:app --reloadBuilt with Strands Agents SDK · Amazon Bedrock Nova Pro · AgentCore Runtime
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article