<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mohsin Sheikhani</title>
    <description>The latest articles on DEV Community by Mohsin Sheikhani (@mohsinsheikhani).</description>
    <link>https://dev.to/mohsinsheikhani</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3205660%2F86f13820-8822-4969-8c94-661bd5847686.png</url>
      <title>DEV Community: Mohsin Sheikhani</title>
      <link>https://dev.to/mohsinsheikhani</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mohsinsheikhani"/>
    <language>en</language>
    <item>
      <title>How to Use Claude Code with Qwen models for Free (Linux)</title>
      <dc:creator>Mohsin Sheikhani</dc:creator>
      <pubDate>Sat, 03 Jan 2026 20:27:44 +0000</pubDate>
      <link>https://dev.to/mohsinsheikhani/how-to-use-claude-code-with-qwen-models-for-free-linux-1fc4</link>
      <guid>https://dev.to/mohsinsheikhani/how-to-use-claude-code-with-qwen-models-for-free-linux-1fc4</guid>
      <description>&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Qwen CLI installed and authenticated&lt;/li&gt;
&lt;li&gt;Node.js v18+ installed&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 1: Install Claude, Claude Code Router and Qwen Code
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install -g @qwen-code/qwen-code@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install -g @anthropic-ai/claude-code @musistudio/claude-code-router
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Extract Your Access Token
&lt;/h3&gt;

&lt;p&gt;Replace LINUX_USER with your Linux username.&lt;/p&gt;

&lt;p&gt;Open &lt;code&gt;/home/LINUX_USER/.qwen/oauth_creds.json&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;It should look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "access_token": "YOUR_QWEN_ACCESS_TOKEN_HERE",
  "token_type": "Bearer",
  "refresh_token": "YOUR_QWEN_REFRESH_TOKEN_HERE",
  "resource_url": "portal.qwen.ai",
  "expiry_date": 1764876220290
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy the &lt;code&gt;access_token&lt;/code&gt; value.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Create router config
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cat &amp;gt; ~/.claude-code-router/config.json &amp;lt;&amp;lt; 'EOF'
{
  "LOG": true,
  "LOG_LEVEL": "info",
  "HOST": "127.0.0.1",
  "PORT": 3456,
  "API_TIMEOUT_MS": 600000,
  "Providers": [
    {
      "name": "qwen",
      "api_base_url": "https://portal.qwen.ai/v1/chat/completions",
      "api_key": "$QWEN_ACCESS_TOKEN",
      "models": [
        "qwen3-coder-plus",
        "qwen3-coder-plus",
        "qwen3-coder-plus"
      ]
    }
  ],
  "Router": {
    "default": "qwen,qwen3-coder-plus",
    "background": "qwen,qwen3-coder-plus",
    "think": "qwen,qwen3-coder-plus",
    "longContext": "qwen,qwen3-coder-plus",
    "longContextThreshold": 60000,
    "webSearch": "qwen,qwen3-coder-plus"
  }
}
EOF
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Verify file was created
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cat ~/.claude-code-router/config.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Set your Access Token
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo 'export QWEN_ACCESS_TOKEN="YOUR_QWEN_ACCESS_TOKEN_HERE"' &amp;gt;&amp;gt; ~/.zshrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;source ~/.zshrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 5: Verify Setup
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;claude --version        # Should show: Claude Code v2.x.x
ccr version             # Should show version number
echo $QWEN_ACCESS_TOKEN # Should show your token
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 6: Start Using
&lt;/h3&gt;

&lt;p&gt;Restart the router server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ccr restart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run Claude Code with Qwen models:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ccr code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; hi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Token Refresh (When you get 401 errors)
&lt;/h3&gt;

&lt;p&gt;Your OAuth token expires. Refresh it by:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Re-authenticating your QWEN CODE CLI: If already logged in and the access_token matches in both &lt;code&gt;config.json&lt;/code&gt; and &lt;code&gt;oauth_creds.json&lt;/code&gt;, delete the &lt;code&gt;oauth_creds.json&lt;/code&gt; file and run &lt;code&gt;qwen&lt;/code&gt; to initiate re-authentication.&lt;/li&gt;
&lt;li&gt;Update the api_key in your config.json with the new access_token: &lt;code&gt;nano ~/.claude-code-router/config.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Restart: &lt;code&gt;ccr restart&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Hopefully&lt;/strong&gt; this will help you learn Claude Code for Free 💖&lt;/p&gt;

</description>
      <category>linux</category>
      <category>llm</category>
      <category>node</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Building Production Multi-Agent Systems: My Experience with Amazon Bedrock AgentCore, and AWS Strands Agents</title>
      <dc:creator>Mohsin Sheikhani</dc:creator>
      <pubDate>Mon, 29 Sep 2025 18:34:47 +0000</pubDate>
      <link>https://dev.to/mohsinsheikhani/building-production-multi-agent-systems-my-experience-with-amazon-bedrock-agentcore-and-aws-41h2</link>
      <guid>https://dev.to/mohsinsheikhani/building-production-multi-agent-systems-my-experience-with-amazon-bedrock-agentcore-and-aws-41h2</guid>
      <description>&lt;h3&gt;
  
  
  The 2 AM Hotel Booking Nightmare
&lt;/h3&gt;

&lt;p&gt;Picture this: It's 2 AM, you're frantically booking a hotel room for a business trip that got moved up by three days. You find the perfect room, book it, then realize six hours later that your flight got cancelled.&lt;br&gt;
Now you need to cancel the booking, but wait, what's the cancellation policy? Will there be fees? Can you modify instead of cancel? And why is customer service only available during business hours when your life operates on chaos-time?&lt;/p&gt;

&lt;p&gt;If this sounds familiar, you've experienced the fundamental problem with traditional hotel booking systems: they treat complex, multi-step workflows as isolated transactions. But real life isn't transactional, it's conversational, contextual, and full of "what-ifs."&lt;/p&gt;

&lt;p&gt;This is the story of how I built an AI system that doesn't just book hotels, it thinks like a seasoned concierge who remembers your preferences, understands hotel policies, and can handle the messy reality of travel planning.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why Traditional Hotel Booking Systems Miss the Mark
&lt;/h2&gt;

&lt;p&gt;Most hotel booking platforms treat you like you're ordering a pizza: pick your toppings (dates, location, price range), pay, and done. But hotel booking isn't pizza ordering, it's relationship management.&lt;/p&gt;

&lt;p&gt;Think about what a great hotel concierge does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Understands complex policies&lt;/strong&gt; and explains them in plain English
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Handles changes gracefully&lt;/strong&gt; without making you start over&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coordinates multiple services&lt;/strong&gt; (booking, modifications, cancellations, notifications)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remembers your preferences&lt;/strong&gt; from previous stays&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Provides proactive guidance&lt;/strong&gt; based on your specific situation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Traditional systems fail because they're built around databases and forms, not conversations and context. They force users to navigate separate interfaces for search, booking, modification, and support, each requiring you to re-explain your situation.&lt;/p&gt;

&lt;p&gt;The result? Frustrated customers, abandoned bookings, and support teams drowning in "simple" requests that require human intelligence to resolve.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Multi-Agent Vision
&lt;/h2&gt;

&lt;p&gt;What if instead of building another booking form, I built a team of AI specialists that work together like a hotel's back-office staff?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Search Specialist&lt;/strong&gt; who knows every hotel's availability and can compare options intelligently&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Booking Specialist&lt;/strong&gt; who understands reservation lifecycles and policy implications
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Policy Advisor&lt;/strong&gt; who can explain complex terms and check compliance before actions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Communications Specialist&lt;/strong&gt; who handles confirmations and keeps everyone informed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supervisor&lt;/strong&gt; who orchestrates the team and maintains conversation context&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2gnk621dmj064c33vbjh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2gnk621dmj064c33vbjh.png" alt="Multi-Agent Architecture, Amazon Bedrock AgentCore, AWS Strands Agents, AWS Lambda - Mohsin Sheikhani" width="800" height="431"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This isn't just a technical architecture, it's a business model that scales human-like service.&lt;/p&gt;
&lt;h2&gt;
  
  
  Enter Amazon Bedrock AgentCore: The Foundation for Intelligent Orchestration
&lt;/h2&gt;

&lt;p&gt;Here's where the vision meets reality. Amazon Bedrock AgentCore isn't just another AI service, it's the infrastructure that makes multi-agent systems production-ready.&lt;/p&gt;

&lt;p&gt;The challenge with building agent teams isn't creating individual agents (that's the easy part). The real complexity lies in:&lt;/p&gt;

&lt;p&gt;AgentCore solves these with three key services that work together:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Memory Service&lt;/strong&gt;: Persistent conversation context that survives sessions. Your booking agent remembers you mentioned you prefer ground floor rooms, even if you come back next week.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gateway Service&lt;/strong&gt;: Secure, managed access to external tools and APIs. No more wrestling with authentication, rate limiting, or connection management.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Runtime Service&lt;/strong&gt;: Scalable agent execution with built-in observability. Your agents run reliably in production without you managing infrastructure.&lt;/p&gt;

&lt;p&gt;But here's what makes it powerful: these services are designed to work together. Memory informs decision-making, Gateway enables action-taking, and Runtime orchestrates it all.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Architecture That Changes Everything
&lt;/h2&gt;

&lt;p&gt;Instead of building a monolithic booking system, I created a supervisor-agent architecture where specialized agents collaborate through AgentCore's components:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2bal3of52xrbdxx7vxiq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2bal3of52xrbdxx7vxiq.png" alt="Multi-Agent Architecture, Amazon Bedrock AgentCore, AgentCore Identity, AWS Strands Agents, MCP, A2A, AWS Lambda - Mohsin Sheikhani" width="800" height="176"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The supervisor doesn't just route requests—it maintains context, orchestrates multi-step workflows, and ensures policy compliance before any action is taken.&lt;/p&gt;
&lt;h2&gt;
  
  
  Building the Agent Team: From Search to Confirmation
&lt;/h2&gt;

&lt;p&gt;Each agent in the system is a specialist, but they're not working in isolation. Here's how the team comes together:&lt;/p&gt;
&lt;h3&gt;
  
  
  The Search &amp;amp; Discovery Agent
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Role&lt;/strong&gt;: The research specialist who knows every hotel's availability and pricing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/mohsinsheikhani/multi-agent-hotel-assistant/blob/main/app/src/agents/search_discovery.py" rel="noopener noreferrer"&gt;Link To Repo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8vy0wps1etvw2ta0jcuk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8vy0wps1etvw2ta0jcuk.png" alt="Amazon Bedrock AgentCore, AgentCore Identity, AgentCore Gateway, AWS Strands Agents, MCP, A2A, AWS Lambda - Mohsin Sheikhani" width="800" height="174"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This agent doesn't just return search results, it understands context. When you ask for "hotels near the conference center," it factors in your previous preferences, compares pricing across dates, and highlights amenities that matter to business travelers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SearchDiscoveryAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseAgent&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Agent responsible for hotel search and discovery operations&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;9001&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_agent_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SearchDiscoveryAgent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_agent_description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Handles hotel search, availability checking, and price comparisons using AgentCore Lambda tools&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Reservation Agent
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Role&lt;/strong&gt;: The booking specialist who manages the entire reservation lifecycle.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/mohsinsheikhani/multi-agent-hotel-assistant/blob/main/app/src/agents/reservation.py" rel="noopener noreferrer"&gt;Link To Repo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxlhaqrj1zfhyboe1usuf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxlhaqrj1zfhyboe1usuf.png" alt="Amazon Bedrock AgentCore, AgentCore Identity, AgentCore Gateway, AWS Strands Agents, MCP, A2A, AWS Lambda - Mohsin Sheikhani" width="800" height="177"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But here's where it gets interesting, this agent is policy-aware. Before making any booking, modification, or cancellation, it checks with the Guest Advisory Agent to understand implications.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ReservationAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseAgent&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Agent responsible for managing hotel reservations&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;9002&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_agent_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ReservationAgent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_agent_description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Handles the full lifecycle of hotel room reservations, including booking, updating, canceling, and fetching past reservations for a guest.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Guest Advisory Agent
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Role&lt;/strong&gt;: The policy expert who bridges business rules and user actions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/mohsinsheikhani/multi-agent-hotel-assistant/blob/main/app/src/agents/guest_advisory.py" rel="noopener noreferrer"&gt;Link To Repo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjtvfiwl71vcqvwapei2w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjtvfiwl71vcqvwapei2w.png" alt="Amazon Bedrock AgentCore, AgentCore Identity, AgentCore Gateway, AWS Strands Agents, MCP, A2A, AWS Lambda, Amazon Bedrock Knowledge Base - Mohsin Sheikhani" width="800" height="174"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This agent has access to a knowledge base of hotel policies, cancellation terms, and booking rules. It doesn't just recite policies—it explains them in context and calculates real implications.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GuestAdvisoryAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseAgent&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Agent responsible for providing hotel policies and advisory information&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;9003&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_agent_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GuestAdvisoryAgent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_agent_description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Provides hotel policies, rules, and advisory information including cancellation policies, check-in/out procedures, and general hotel guidelines.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Notification Agent
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Role&lt;/strong&gt;: The communications specialist who keeps everyone informed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/mohsinsheikhani/multi-agent-hotel-assistant/blob/main/app/src/agents/notification.py" rel="noopener noreferrer"&gt;Link To Repo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5wf0u31pp4bq5ejnyc60.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5wf0u31pp4bq5ejnyc60.png" alt="Amazon Bedrock AgentCore, AWS Strands Agents, Strands Agents Tools, MCP, A2A, AWS Lambda - Mohsin Sheikhani" width="800" height="331"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every booking, modification, or cancellation triggers appropriate notifications. But it's not just email templates, it's contextual communication that includes next steps and relevant details.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NotificationAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseAgent&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Agent responsible for handling booking notifications and communications&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;9004&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_agent_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NotificationAgent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_agent_description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Handles booking confirmations, modifications, cancellations, and other communication needs for hotel reservations.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Supervisor's Secret Weapon: Memory
&lt;/h2&gt;

&lt;p&gt;Here's what makes this system truly intelligent—the supervisor agent uses AgentCore's Memory service to maintain conversation context across sessions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/mohsinsheikhani/multi-agent-hotel-assistant/blob/main/app/src/core/memory.py" rel="noopener noreferrer"&gt;Link To Repo&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MemoryManager&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Manages Bedrock AgentCore memory operations&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;region_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;memory_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MemoryClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;region_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;region_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;memory_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;memory_name&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;memory_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize_memory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Initialize or retrieve existing memory&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MemoryHookProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HookProvider&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Provides memory hooks for agent lifecycle events&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;memory_client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;MemoryClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;memory_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;memory_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;memory_client&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;memory_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;memory_id&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_agent_initialized&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AgentInitializedEvent&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Load recent conversation history when agent starts&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;...&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Memory load error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_message_added&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;MessageAddedEvent&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Store messages in memory&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;...&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Memory save error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;register_hooks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;HookRegistry&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Register memory hooks&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MessageAddedEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;on_message_added&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AgentInitializedEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;on_agent_initialized&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Conversation context persists across sessions
# Agents remember preferences, previous bookings, ongoing requests
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you return to continue a booking conversation from yesterday, the system doesn't just remember what you said—it remembers the context, your preferences, and where you left off in the process.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Magic of Agent Orchestration: How Complex Workflows Become Simple Conversations
&lt;/h2&gt;

&lt;p&gt;Here's where the supervisor agent earns its keep. It doesn't just route requests, it orchestrates intelligent workflows that would normally require multiple customer service interactions.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Real Workflow in Action
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;User&lt;/strong&gt;: "I need to cancel my booking for next week, but I'm worried about fees."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Traditional System&lt;/strong&gt;: Navigate to "My Bookings" → Find booking → Click cancel → Read policy wall of text → Call customer service for clarification → Wait on hold → Explain situation → Get transferred → Explain again...&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Our Multi-Agent System&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Supervisor&lt;/strong&gt; identifies this as a policy-sensitive cancellation request&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Guest Advisory Agent&lt;/strong&gt; retrieves specific cancellation policy for this booking&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reservation Agent&lt;/strong&gt; calculates exact fees and alternatives&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supervisor&lt;/strong&gt; presents clear options: "Cancelling now incurs a $50 fee, but modifying dates is free until tomorrow"&lt;/li&gt;
&lt;li&gt;User chooses, &lt;strong&gt;Reservation Agent&lt;/strong&gt; executes, &lt;strong&gt;Notification Agent&lt;/strong&gt; confirms&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All in one conversation. No navigation, no transfers, no re-explaining.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Orchestration Code That Makes It Possible
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Supervisor's intelligent routing with context awareness
&lt;/span&gt;&lt;span class="n"&gt;system_prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
You are the Supervisor Agent for a multi-agent hotel booking system.

Policy-aware behavior:
- Before performing any booking, modification, or cancellation, check relevant hotel policies (using the Guest Advisory Agent or Knowledge Base) to determine if there are penalties, restrictions, or special conditions.
- Present the user with a summary of what will happen and the relevant policy (e.g., &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Cancelling within 24 hours will incur a 20% fee&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;).
- Ask for explicit confirmation before taking action.
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="c1"&gt;# The supervisor maintains context through AgentCore Memory
&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;bedrock_model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# A2A communication tools
&lt;/span&gt;    &lt;span class="n"&gt;system_prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_get_system_prompt&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;hooks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;MemoryHookProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;memory_manager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;memory_id&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
    &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;actor_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;actor_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;session_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_id&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/mohsinsheikhani/multi-agent-hotel-assistant/blob/main/app/src/core/supervisor.py" rel="noopener noreferrer"&gt;Link To Repo&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Why This Architecture Scales
&lt;/h3&gt;

&lt;p&gt;Each agent runs independently on its own port, communicating through Agent-to-Agent (A2A) protocols. This means:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkbkuobqvqtwdn1safiy2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkbkuobqvqtwdn1safiy2.png" alt="Amazon Bedrock AgentCore, AgentCore Identity, AgentCore Gateway, AWS Strands Agents, MCP, A2A, AWS Lambda - Mohsin Sheikhani" width="800" height="374"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fault isolation&lt;/strong&gt;: One agent's issues don't cascade&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Independent scaling&lt;/strong&gt;: High-demand agents can scale separately&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easy updates&lt;/strong&gt;: Modify one agent without touching others&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clear responsibilities&lt;/strong&gt;: Each agent has a single, well-defined purpose&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Production Reality: Memory, Monitoring, and the Details That Matter
&lt;/h2&gt;

&lt;p&gt;Building a demo is one thing. Building something that handles real customer conversations at 3 AM when your infrastructure is under load? That's where AgentCore's production features become essential.&lt;/p&gt;

&lt;h3&gt;
  
  
  Memory That Actually Works
&lt;/h3&gt;

&lt;p&gt;The breakthrough isn't just that the system remembers, it's how it remembers intelligently.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Memory hooks that maintain context across sessions
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MemoryHookProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HookProvider&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_agent_initialized&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AgentInitializedEvent&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Load last 10 conversation turns when agent starts
&lt;/span&gt;        &lt;span class="n"&gt;recent_turns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;memory_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_last_k_turns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;memory_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;memory_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
            &lt;span class="n"&gt;actor_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;actor_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
            &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
            &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# Inject context into agent's system prompt
&lt;/span&gt;        &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;system_prompt&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s"&gt;Recent conversation:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a customer returns after a week to modify their booking, the system doesn't just remember the booking ID, it remembers they mentioned preferring ground floor rooms, that they're traveling for a conference, and that they were concerned about cancellation policies.&lt;/p&gt;

&lt;h3&gt;
  
  
  Error Handling That Prevents Disasters
&lt;/h3&gt;

&lt;p&gt;In production, things fail. APIs timeout, agents crash, networks hiccup. The difference between a good system and a great one is graceful degradation:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/mohsinsheikhani/multi-agent-hotel-assistant/blob/main/app/src/core/supervisor.py" rel="noopener noreferrer"&gt;Link To Repo&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Supervisor handles agent failures gracefully
&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Process a user request through the supervisor agent&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Processing request: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke_async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Request processed successfully&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error processing request: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Observability That Tells the Real Story
&lt;/h3&gt;

&lt;p&gt;AgentCore's built-in observability means you can see exactly what's happening:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which agent handled each request&lt;/li&gt;
&lt;li&gt;How long each step took&lt;/li&gt;
&lt;li&gt;Where conversations get stuck&lt;/li&gt;
&lt;li&gt;Which policies cause the most confusion&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This isn't just monitoring, it's business intelligence about how customers actually interact with your system.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Authentication Reality
&lt;/h3&gt;

&lt;p&gt;Real systems need real security. AgentCore handles the complexity:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/mohsinsheikhani/multi-agent-hotel-assistant/blob/main/app/src/utils/auth.py" rel="noopener noreferrer"&gt;Link To Repo&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Cognito integration for secure gateway access
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TokenManager&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_fresh_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Client credentials flow with proper scoping
&lt;/span&gt;        &lt;span class="c1"&gt;# Automatic token refresh and error handling
&lt;/span&gt;        &lt;span class="c1"&gt;# No security vulnerabilities from DIY auth
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Business Impact: What This Actually Means for Hotels and Customers
&lt;/h2&gt;

&lt;p&gt;The technical architecture is impressive, but let's talk about what really matters, business outcomes.&lt;/p&gt;

&lt;h3&gt;
  
  
  For Hotels: Operational Efficiency at Scale
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Before&lt;/strong&gt;: Customer calls about cancellation policy → Agent looks up booking → Checks policy document → Calculates fees → Explains options → Customer decides → Agent processes → Sends confirmation.&lt;br&gt;
&lt;strong&gt;Average handling time: 8-12 minutes.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;After&lt;/strong&gt;: Customer asks about cancellation → System instantly knows booking details, policy implications, and alternatives → Presents clear options → Customer decides → Action executed automatically.&lt;br&gt;
&lt;strong&gt;Average handling time: 1-2 minutes.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's not just efficiency, that's 4x capacity increase with the same support team.&lt;/p&gt;
&lt;h3&gt;
  
  
  For Customers: Intelligence That Feels Personal
&lt;/h3&gt;

&lt;p&gt;The system doesn't just remember your booking, it remembers your story:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"I see you're traveling for the same conference as last year. Would you like the same room type?"&lt;/li&gt;
&lt;li&gt;"Based on your previous concern about cancellation fees, I've found options with flexible policies."&lt;/li&gt;
&lt;li&gt;"Your flight was delayed last time, should I book a late check-in for this trip?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This isn't marketing personalization, it's operational intelligence that makes every interaction feel like talking to someone who actually knows you.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Competitive Advantage
&lt;/h3&gt;

&lt;p&gt;While competitors are still building better search interfaces, this system is solving the real problem: &lt;strong&gt;complex travel decisions require conversation, not just transactions.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Hotels using this approach can offer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;24/7 intelligent support&lt;/strong&gt; without 24/7 staffing costs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proactive policy guidance&lt;/strong&gt; that prevents booking mistakes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Seamless modification workflows&lt;/strong&gt; that retain customers instead of losing them&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contextual upselling&lt;/strong&gt; based on actual preferences, not generic algorithms&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  ROI That Actually Matters
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Support cost reduction&lt;/strong&gt;: 4x efficiency gain on routine inquiries&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Booking completion rates&lt;/strong&gt;: Fewer abandoned bookings due to policy confusion&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Customer retention&lt;/strong&gt;: Seamless modification experience vs. starting over elsewhere&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Upselling effectiveness&lt;/strong&gt;: Context-aware recommendations vs. generic offers&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  The Future of Conversational Commerce: Where This Goes Next
&lt;/h2&gt;

&lt;p&gt;This hotel booking system is just the beginning. The patterns we've established, supervisor orchestration, policy-aware workflows, persistent memory, apply to any complex business process that currently requires human intervention.&lt;/p&gt;
&lt;h3&gt;
  
  
  Beyond Hotel Booking
&lt;/h3&gt;

&lt;p&gt;Imagine applying this architecture to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Insurance claims processing&lt;/strong&gt; with policy specialists and damage assessors&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Financial planning&lt;/strong&gt; with investment advisors and compliance experts
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Healthcare coordination&lt;/strong&gt; with specialists who understand your medical history&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enterprise procurement&lt;/strong&gt; with budget analysts and vendor specialists&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each domain gets a team of AI specialists who collaborate intelligently, remember context, and handle complexity gracefully.&lt;/p&gt;
&lt;h1&gt;
  
  
  The AgentCore Foundation: Why This Changes Everything
&lt;/h1&gt;

&lt;p&gt;Amazon Bedrock AgentCore solved the infrastructure problems that kill most multi-agent projects. Here's how each component transformed this hotel booking system from concept to reality.&lt;/p&gt;
&lt;h2&gt;
  
  
  AgentCore Gateway: Your Lambda Functions Become Agent Tools
&lt;/h2&gt;

&lt;p&gt;The breakthrough moment came when I realized I could keep all my business logic in familiar AWS Lambda functions while making them accessible to agents through the Model Context Protocol (MCP).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Problem&lt;/strong&gt;: Agents need access to real business systems, hotel inventory databases, booking APIs, policy engines. Traditionally, this means complex authentication, API management, and security concerns.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AgentCore Gateway's Solution&lt;/strong&gt;: Your Lambda functions become agent tools automatically.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F585b4f3ky38qdwyv3jem.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F585b4f3ky38qdwyv3jem.png" alt="Amazon Bedrock AgentCore, AgentCore Identity, AgentCore Gateway, AWS Strands Agents, MCP, A2A, AWS Lambda - Mohsin Sheikhani" width="800" height="281"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;APIGatewayProxyEvent&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;APIGatewayProxyResult&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Received event:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;queryResp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;dynamo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;QueryCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;TableName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;KeyConditionExpression&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;...,&lt;/span&gt;
          &lt;span class="na"&gt;ExpressionAttributeValues&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="p"&gt;...&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;scanResp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;dynamo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ScanCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;TableName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Error:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Internal Server Error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Gateway handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Authentication and authorization&lt;/strong&gt; with fine-grained access control&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate limiting and throttling&lt;/strong&gt; to protect your backend systems&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Request/response transformation&lt;/strong&gt; between agent protocols and your APIs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP protocol abstraction&lt;/strong&gt; so your Lambdas don't need to know about agents&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This means your hotel search, booking, and policy engines remain standard AWS Lambda functions, but agents can invoke them as naturally as calling any other tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  AgentCore Identity: Security That Scales
&lt;/h2&gt;

&lt;p&gt;Multi-agent systems create complex security challenges. When the Supervisor Agent needs to call the Reservation Agent, which then calls the Policy Agent, how do you maintain security context across the entire chain?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Traditional Approach&lt;/strong&gt;: Custom authentication between every agent pair, token management nightmares, and security vulnerabilities.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AgentCore Identity&lt;/strong&gt;: Centralized identity management with automatic token handling.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Agents authenticate once, communicate securely forever
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TokenManager&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scope_string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resource_server_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/gateway:read &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resource_server_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/gateway:write&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_fresh_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="c1"&gt;# AgentCore handles the OAuth2 client credentials flow
&lt;/span&gt;        &lt;span class="c1"&gt;# Automatic token refresh and scope validation
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Identity service provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;OAuth2 client credentials flow&lt;/strong&gt; with automatic token refresh&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fine-grained scopes&lt;/strong&gt; for different agent capabilities&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Centralized policy management&lt;/strong&gt; across your entire agent ecosystem&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit trails&lt;/strong&gt; for every agent interaction&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flv5fpsredzczqgqyousp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flv5fpsredzczqgqyousp.png" alt="Amazon Bedrock AgentCore, AgentCore Identity, AgentCore Gateway, AWS Strands Agents, MCP, A2A, AWS Lambda - Mohsin Sheikhani" width="800" height="214"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  AgentCore Memory: Context That Persists and Scales
&lt;/h2&gt;

&lt;p&gt;Here's where the magic happens. Traditional chatbots lose context between sessions. AgentCore Memory makes agents truly intelligent by maintaining conversation context that survives sessions, scales across millions of users, and enables sophisticated reasoning.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Architecture&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fax0uk34w5xd1bueps3cf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fax0uk34w5xd1bueps3cf.png" alt="Amazon Bedrock AgentCore, AgentCore Identity, AgentCore Gateway, AgentCore Memory, AWS Strands Agents, MCP, A2A, AWS Lambda - Mohsin Sheikhani" width="703" height="567"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What This Enables&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cross-session continuity&lt;/strong&gt;: Customer returns next week, agent remembers their preferences&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-agent context sharing&lt;/strong&gt;: Reservation Agent knows what Search Agent discovered&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Intelligent reasoning&lt;/strong&gt;: "Based on your previous concern about cancellation fees..."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalable storage&lt;/strong&gt;: Millions of conversations with configurable retention policies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Business Impact&lt;/strong&gt;: Customers don't repeat themselves. Agents make contextual decisions. Conversations feel natural, not transactional.&lt;/p&gt;

&lt;h2&gt;
  
  
  AgentCore Runtime: Production-Ready Agent Execution
&lt;/h2&gt;

&lt;p&gt;Running one agent in development is easy. Running a team of agents in production, handling failures gracefully, scaling under load, and maintaining performance—that's where most multi-agent projects die.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AgentCore Runtime's Production Features&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BedrockAgentCoreApp&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Agents that handle production realities
&lt;/span&gt;&lt;span class="nd"&gt;@app.entrypoint&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Main entry point for the hotel booking system&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;question&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;question&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;No question provided&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;supervisor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;process_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Failed to process request: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Failed to process request: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Runtime Capabilities&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Automatic scaling&lt;/strong&gt; based on demand&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Health monitoring&lt;/strong&gt; with automatic recovery&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resource management&lt;/strong&gt; to prevent runaway processes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Load balancing&lt;/strong&gt; across agent instances&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Graceful degradation&lt;/strong&gt; when components fail&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feklmzz6v4t1ieiaf3vv1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feklmzz6v4t1ieiaf3vv1.png" alt="Amazon Bedrock AgentCore, AgentCore Identity, AgentCore Gateway, AWS Strands Agents, MCP, A2A, AWS Lambda - Mohsin Sheikhani" width="703" height="287"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Result&lt;/strong&gt;: Your agents run reliably in production without you managing infrastructure complexity.&lt;/p&gt;

&lt;h2&gt;
  
  
  AgentCore Observability: Intelligence You Can See
&lt;/h2&gt;

&lt;p&gt;The most sophisticated multi-agent system is useless if you can't understand what's happening inside it. AgentCore Observability provides deep insights into agent behavior, performance, and business impact.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What You Can See&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Agent interaction flows&lt;/strong&gt;: Which agent handled each step of a complex workflow&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance metrics&lt;/strong&gt;: Response times, success rates, resource utilization&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Business intelligence&lt;/strong&gt;: Which policies cause confusion, where customers get stuck&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error patterns&lt;/strong&gt;: Systematic issues before they become customer problems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fos4pghwdjbsdzkc9gwlc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fos4pghwdjbsdzkc9gwlc.png" alt="Amazon Bedrock AgentCore, AgentCore Observability, AWS Strands Agents, MCP, A2A, AWS Lambda - Mohsin Sheikhani" width="800" height="282"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Business Value&lt;/strong&gt;: You don't just run agents, you optimize them based on real usage patterns and customer behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Architecture Scales
&lt;/h2&gt;

&lt;p&gt;Each AgentCore service solves a specific production challenge:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Gateway&lt;/strong&gt;: Tool access without security complexity&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Identity&lt;/strong&gt;: Authentication without custom code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory&lt;/strong&gt;: Context without storage management&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runtime&lt;/strong&gt;: Reliability without infrastructure management&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observability&lt;/strong&gt;: Insights without custom monitoring&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Together, they create a foundation where you focus on business logic while AgentCore handles the production complexity that typically kills multi-agent projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building Your Own Agent Team
&lt;/h2&gt;

&lt;p&gt;Ready to experiment? The architecture is surprisingly approachable:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Start with the supervisor pattern&lt;/strong&gt; - One agent that orchestrates others&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add memory integration&lt;/strong&gt; - Context that persists across sessions
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build specialized agents&lt;/strong&gt; - Each with a single, clear responsibility&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use A2A communication&lt;/strong&gt; - Agents that collaborate, not compete&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deploy on AgentCore&lt;/strong&gt; - Production-ready from day one&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The complete implementation is available on GitHub, including deployment scripts and documentation for getting started with your own multi-agent system.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What complex workflow in your domain could benefit from conversational intelligence? The tools are ready, the question is what you'll build with them.&lt;/strong&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  GitHub Repo
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://github.com/mohsinsheikhani/multi-agent-hotel-assistant" rel="noopener noreferrer"&gt;Multi-Agent Hotel Assistant&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This project was built for "AWS AI Engineering Month: Building with Agents". The combination of Amazon Bedrock AgentCore's Memory, Gateway, and Runtime services with the Strands Agents framework creates a powerful foundation for production multi-agent systems.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>agentaichallenge</category>
      <category>bedrock</category>
      <category>ai</category>
    </item>
    <item>
      <title>A Practical Guide to MLOps on AWS: Demand Forecasting with Amazon Bedrock and Automated EC2 Pipelines (Phase 03)</title>
      <dc:creator>Mohsin Sheikhani</dc:creator>
      <pubDate>Thu, 10 Jul 2025 17:19:58 +0000</pubDate>
      <link>https://dev.to/mohsinsheikhani/a-practical-guide-to-mlops-on-aws-demand-forecasting-with-amazon-bedrock-and-automated-ec2-3c4c</link>
      <guid>https://dev.to/mohsinsheikhani/a-practical-guide-to-mlops-on-aws-demand-forecasting-with-amazon-bedrock-and-automated-ec2-3c4c</guid>
      <description>&lt;p&gt;In &lt;a href="https://dev.to/mohsinsheikhani/a-practical-guide-to-mlops-on-aws-transforming-raw-data-into-ai-ready-datasets-with-aws-glue-4lc9"&gt;Phase 02&lt;/a&gt;, we transformed raw user interaction events into structured, enriched datasets, organized across bronze, silver, and gold zones in S3, and made them query able through Glue + Athena.&lt;/p&gt;

&lt;p&gt;Now in Phase 03, we shift from preparing the data to putting it to work.&lt;/p&gt;

&lt;p&gt;This is where AI meets infrastructure:&lt;br&gt;
We’ll use Amazon Bedrock to predict product demand based on historical sales, and architect it the way real systems do.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fev7hspysppg0gacyu0cp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fev7hspysppg0gacyu0cp.png" alt="Demand Forecasting with Amazon Bedrock and Automated EC2 Pipelines" width="800" height="565"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Why does this matter?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Forecasting is a batch job, not a real-time interaction.&lt;/li&gt;
&lt;li&gt;It needs compute, but we don’t want to keep EC2 running 24/7.&lt;/li&gt;
&lt;li&gt;So we’ll spin up an EC2 instance nightly, run a forecasting script that:

&lt;ul&gt;
&lt;li&gt;Reads gold-zone data&lt;/li&gt;
&lt;li&gt;Sends it to Bedrock&lt;/li&gt;
&lt;li&gt;Updates DynamoDB with new demand forecasts&lt;/li&gt;
&lt;li&gt;Shuts itself down to save cost&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of this is orchestrated via EventBridge and Lambda, forming a complete, automated, cost-efficient forecasting pipeline.&lt;/p&gt;

&lt;p&gt;Now, the question here is why we choose EC2 to run the forecasting job?&lt;/p&gt;

&lt;p&gt;Because in real-world ML systems, long-running batch jobs like forecasting are often too heavy for Lambda, may require more memory, longer runtimes, or even GPU-based instances. Using EC2 gives us:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run larger forecasting workloads&lt;/li&gt;
&lt;li&gt;Use GPU-based instances (if needed)&lt;/li&gt;
&lt;li&gt;Keep costs low by shutting down after completion&lt;/li&gt;
&lt;li&gt;Full control over compute resources&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  How the Orchestration Works
&lt;/h3&gt;

&lt;p&gt;We’ve designed this system to be automated and cost-optimized:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Amazon EventBridge triggers a Lambda function nightly (e.g. every 24 hours)&lt;/li&gt;
&lt;li&gt;Lambda starts up an EC2 instance&lt;/li&gt;
&lt;li&gt;EC2 pulls cleaned sales data from S3 and runs a Python script&lt;/li&gt;
&lt;li&gt;The script:

&lt;ul&gt;
&lt;li&gt;Sends data to Amazon Bedrock to forecast the next 7 days&lt;/li&gt;
&lt;li&gt;Updates DynamoDB with the &lt;code&gt;forecasted_demand&lt;/code&gt; per product&lt;/li&gt;
&lt;li&gt;Shuts down the EC2 instance when the task is done&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Step 1 - Provisioning EC2 Forecasting Instance
&lt;/h2&gt;

&lt;p&gt;In this step, we set up an Amazon EC2 instance that will run our demand forecasting script.&lt;/p&gt;

&lt;p&gt;We’re using AWS CDK to provision this instance with the following characteristics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pulls aggregated sales data from the S3 Gold Zone (&lt;code&gt;forecast_ready/&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Sends this data to Amazon Bedrock to forecast product demand&lt;/li&gt;
&lt;li&gt;Updates the &lt;code&gt;forecasted_demand&lt;/code&gt; field in the DynamoDB Inventory Table&lt;/li&gt;
&lt;li&gt;Shuts itself down after the job is completed to avoid unnecessary costs&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Configuration Highlights
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Launched in a VPC with a public subnet (since it needs internet access for Bedrock and S3, we'll move it to private subnet in upcoming phase)&lt;/li&gt;
&lt;li&gt;Attached to an IAM role that allows:

&lt;ul&gt;
&lt;li&gt;Invoking Bedrock models (&lt;code&gt;bedrock:InvokeModel&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Reading from S3&lt;/li&gt;
&lt;li&gt;Writing to DynamoDB&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Bootstrapped via a user data script that:

&lt;ul&gt;
&lt;li&gt;Installs required dependencies (aws cli, etc.)&lt;/li&gt;
&lt;li&gt;Downloads the &lt;code&gt;inventory_forecaster.py&lt;/code&gt; script from S3&lt;/li&gt;
&lt;li&gt;Runs the script&lt;/li&gt;
&lt;li&gt;Terminates the instance once done&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Create a Dedicated File for EC2 Forecasting
&lt;/h4&gt;

&lt;p&gt;Follow the same pattern by organizing this inside a new folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir -p lib/constructs/common/compute/
touch lib/constructs/common/compute/forecast-instance.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then paste the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Construct } from "constructs";
import {
  Instance,
  InstanceClass,
  InstanceSize,
  InstanceType,
  MachineImage,
  Vpc,
  SecurityGroup,
  Peer,
  Port,
} from "aws-cdk-lib/aws-ec2";
import {
  Role,
  ServicePrincipal,
  ManagedPolicy,
  PolicyStatement,
} from "aws-cdk-lib/aws-iam";
import { Bucket } from "aws-cdk-lib/aws-s3";
import { Table } from "aws-cdk-lib/aws-dynamodb";
import { aws_ec2 as ec2 } from "aws-cdk-lib";

interface ForecastEc2Props {
  vpc: Vpc;
  goldBucket: Bucket;
  dataAssetsBucket: Bucket;
  forecastTable: Table;
}

export class ForecastEc2Instance extends Construct {
  public readonly instance: Instance;

  constructor(scope: Construct, id: string, props: ForecastEc2Props) {
    super(scope, id);

    const { vpc, goldBucket, dataAssetsBucket, forecastTable } = props;

    const role = new Role(this, "ForecastEC2Role", {
      assumedBy: new ServicePrincipal("ec2.amazonaws.com"),
      managedPolicies: [
        ManagedPolicy.fromAwsManagedPolicyName("CloudWatchAgentServerPolicy"),
        ManagedPolicy.fromAwsManagedPolicyName("AmazonS3ReadOnlyAccess"),
        ManagedPolicy.fromAwsManagedPolicyName("AmazonDynamoDBFullAccess"),
      ],
    });

    role.addToPolicy(
      new PolicyStatement({
        actions: ["bedrock:InvokeModel"],
        resources: ["*"],
      })
    );

    role.addToPolicy(
      new PolicyStatement({
        actions: ["ec2:TerminateInstances"],
        resources: ["*"],
        conditions: {
          StringEquals: {
            "ec2:ResourceTag/Name": "ForecastEC2",
          },
        },
      })
    );

    const securityGroup = new SecurityGroup(this, "ForecastEC2SG", {
      vpc,
      description: "Allow EC2 to access S3/Bedrock/DynamoDB",
      allowAllOutbound: true,
    });

    securityGroup.addIngressRule(
      Peer.anyIpv4(),
      Port.tcp(22),
      "Allow SSH from anywhere"
    );

    const userData = ec2.UserData.forLinux();
    userData.addCommands(
      "sudo yum update -y",
      "sudo yum install -y python3 pip -y",
      "pip3 install boto3 pandas pyarrow",

      "cd /home/ec2-user",

      `curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"`,
      "unzip awscliv2.zip",
      "sudo ./aws/install --update",

      // Export environment variables to .bashrc or directly
      `echo 'export GOLD_BUCKET=${goldBucket.bucketName}' &amp;gt;&amp;gt; /etc/profile`,
      `echo 'export FORECAST_TABLE=${forecastTable.tableName}' &amp;gt;&amp;gt; /etc/profile`,
      "source /etc/profile",

      `aws s3 cp s3://${dataAssetsBucket.bucketName}/scripts/inventory_forecaster.py .`,
      "python3 ./inventory_forecaster.py",

      "shutdown now -h"
    );

    this.instance = new Instance(this, "ForecastEC2", {
      instanceName: "ForecastEC2",
      instanceType: InstanceType.of(InstanceClass.T3, InstanceSize.MEDIUM),
      machineImage: MachineImage.latestAmazonLinux2023(),
      vpc,
      securityGroup,
      role,
      userData,
      associatePublicIpAddress: true,
      vpcSubnets: {
        subnetType: ec2.SubnetType.PUBLIC,
      },
    });
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In your &lt;code&gt;retail-ai-insights-stack.ts&lt;/code&gt;, import the construct:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { ForecastEc2Instance } from "./constructs/common/compute/forecast-instance";
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And instantiate it like this (as you already had):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const forecastInstance = new ForecastEc2Instance(this, "ForecastingEc2", {
  vpc: vpc,
  goldBucket,
  dataAssetsBucket,
  forecastTable: dynamoConstruct.inventoryTable,
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Deploying the Forecasting Infrastructure
&lt;/h4&gt;

&lt;p&gt;Once the &lt;code&gt;ForecastEc2Instance&lt;/code&gt; construct is in place, don't do &lt;code&gt;cdk deploy&lt;/code&gt; for now.&lt;/p&gt;

&lt;p&gt;Go to AWS Console, and search for Amazon Bedrock, click on the Foundation Models &amp;gt; Model Catalog, on the Providers tab check mark the Anthropic&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fghy454cj62re9so5zrd0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fghy454cj62re9so5zrd0.png" alt="Amazon Bedrock Foundational Model Catalog - AWS Console" width="800" height="213"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Look for the &lt;code&gt;Claude 3.7 Sonnet&lt;/code&gt;, in my case it's on the first row, third column, click on it&lt;/p&gt;

&lt;p&gt;In the next screen, you'll see &lt;code&gt;Available to request&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhn63u3e6tjvpgocb7x6j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhn63u3e6tjvpgocb7x6j.png" alt="Request model access for Claude 3.7 Sonnet" width="800" height="148"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on, &lt;code&gt;Request model access&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fysw5ur84ox5kbgpt4vme.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fysw5ur84ox5kbgpt4vme.png" alt="Request model access for Claude 3.7 Sonnet" width="800" height="130"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, click for Enable specific models, and search for &lt;code&gt;Claude 3.7 Sonnet&lt;/code&gt;, check mark it, and at the very bottom click on &lt;code&gt;Next&lt;/code&gt;, and provide random details&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbzbr4i9ncm8dxy7clflk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbzbr4i9ncm8dxy7clflk.png" alt="Request model access for Claude 3.7 Sonnet - Put in details" width="800" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After a little while, you should see Access Granted&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F03p84x0ljv1izqz4oz83.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F03p84x0ljv1izqz4oz83.png" alt="Request access granted for Claude 3.7 Sonnet" width="800" height="95"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now that we've access to Bedrock model on our account, we should be good to deploy our resources with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cdk deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Verifying the Results
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Wait for the EC2 instance status to become "running" in the EC2 console.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx61qce1alxxlmrpj8bdb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx61qce1alxxlmrpj8bdb.png" alt="Amazon EC2 instances list" width="800" height="120"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Then watch it auto-terminate after the script completes execution.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4zf1zdaof1ik67k7b3qg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4zf1zdaof1ik67k7b3qg.png" alt="Amazon EC2 instances list - Status terminated" width="800" height="120"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Now head over to DynamoDB &amp;gt; Explore Items, and check your table.&lt;/li&gt;
&lt;li&gt;You’ll see that the &lt;code&gt;forecasted_demand&lt;/code&gt; field has been updated for four products (to save cost by avoidings extra calls to Bedrock).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flgtaouvg89i9vxiopref.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flgtaouvg89i9vxiopref.png" alt="Amazon DynamoDB - Explore items" width="800" height="284"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 - Automating Forecasting with EventBridge &amp;amp; Lambda
&lt;/h2&gt;

&lt;p&gt;In a real-world scenario, you wouldn’t manually trigger forecasting jobs. Instead, you’d want these predictions to run nightly, every 24 hours, and only spin up compute when needed to save cost.&lt;/p&gt;

&lt;p&gt;To do that, we’ll use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Amazon EventBridge to define a scheduled rule (runs every night at 1:00 AM)&lt;/li&gt;
&lt;li&gt;AWS Lambda to start our EC2 forecasting instance&lt;/li&gt;
&lt;li&gt;EC2 itself terminates automatically after completing the prediction job&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Make a new file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir -p lib/constructs/events
touch lib/constructs/events/schedule-ec2-task.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Paste the code that provisions the Lambda, gives it permission to start the EC2 instance, and wires it into the EventBridge rule.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Construct } from "constructs";
import * as cdk from "aws-cdk-lib";
import { aws_lambda as lambda, Duration } from "aws-cdk-lib";
import { Rule, Schedule } from "aws-cdk-lib/aws-events";
import { LambdaFunction } from "aws-cdk-lib/aws-events-targets";
import { PolicyStatement } from "aws-cdk-lib/aws-iam";
import {
  NodejsFunction,
  NodejsFunctionProps,
} from "aws-cdk-lib/aws-lambda-nodejs";
import path from "path";

export class ScheduleForecastTask extends Construct {
  constructor(scope: Construct, id: string, instanceId: string) {
    super(scope, id);

    const startInstanceLambdaProps: NodejsFunctionProps = {
      functionName: "StartInstanceLambda",
      runtime: lambda.Runtime.NODEJS_20_X,
      handler: "handler",
      memorySize: 128,
      entry: path.join(__dirname, "../../../lambda/start-instance/index.js"),
      timeout: cdk.Duration.seconds(10),
      environment: {
        INSTANCE_ID: instanceId,
      },
    };

    const startInstanceLambda = new NodejsFunction(
      this,
      "StartInstanceLambda",
      {
        ...startInstanceLambdaProps,
      }
    );

    startInstanceLambda.addToRolePolicy(
      new PolicyStatement({
        actions: ["ec2:StartInstances"],
        resources: [`arn:aws:ec2:*:*:instance/${instanceId}`],
      })
    );

    new Rule(this, "StartInstanceSchedule", {
      schedule: Schedule.cron({ minute: "0", hour: "1" }),
      targets: [new LambdaFunction(startInstanceLambda)],
    });
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's setup the lambda function&lt;/p&gt;

&lt;p&gt;Make up a file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir -p lambda/start-instance &amp;amp;&amp;amp; touch lambda/start-instance/index.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And paste the following code to trigger an ec2 instance&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { EC2Client, StartInstancesCommand } from "@aws-sdk/client-ec2";

const ec2 = new EC2Client({});

exports.handler = async (event) =&amp;gt; {
  console.info("Start Instance Lambda event", JSON.stringify(event, null, 2));

  const { instanceId } = event;

  try {
    const command = new StartInstancesCommand({
      InstanceIds: [instanceId],
    });

    await ec2.send(command);

    return { message: "Booting Instance command initiated" };
  } catch (error) {
    console.error("Failed to boot up the instance:", error);
    throw error;
  }
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, back in your &lt;code&gt;retail-ai-insights-stack.ts&lt;/code&gt;, import and call the construct like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;new ScheduleForecastTask(
  this,
  "ScheduleForecastTask",
  forecastInstance.instance.instanceId
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once that done, deploy via&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cdk deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once deployed, your forecast job will run automatically every night, keeping your product demand predictions fresh and your compute costs optimized.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wrapping Up Phase 3 – Scalable Forecasting, Zero Waste
&lt;/h3&gt;

&lt;p&gt;With this phase complete, we’ve automated a core business process, demand forecasting, in a way that’s:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI-driven: Leveraging Amazon Bedrock for predictive insights.&lt;/li&gt;
&lt;li&gt;Cost-conscious: EC2 only runs when needed, then shuts down.&lt;/li&gt;
&lt;li&gt;Fully automated: Triggered nightly via EventBridge with no manual intervention.&lt;/li&gt;
&lt;li&gt;Production-ready: Clean orchestration, secure roles, real-time updates to DynamoDB.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This isn’t just about running a script. It’s about combining compute, AI, storage, and automation into a solution that mirrors how real companies make stocking decisions, every single day, with no human in the loop.&lt;/p&gt;

&lt;p&gt;Next up: Let’s use that same user interaction data to drive real-time product recommendations with Amazon Personalize.&lt;/p&gt;

&lt;h2&gt;
  
  
  Complete Code for the Third Phase
&lt;/h2&gt;

&lt;p&gt;To view the full code for the third phase, &lt;a href="https://github.com/mohsinsheikhani/retail-ai-insights/tree/main/03-demand-forecasting-with-bedrock" rel="noopener noreferrer"&gt;checkout the repository on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🚀 &lt;strong&gt;Follow me on &lt;a href="https://www.linkedin.com/in/mohsin-sheikhani/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; for more AWS content!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>mlops</category>
      <category>bedrock</category>
    </item>
    <item>
      <title>Hands-On with Amazon Bedrock Agents: Hotel Booking Assistant with Action Groups and Knowledge Bases</title>
      <dc:creator>Mohsin Sheikhani</dc:creator>
      <pubDate>Thu, 26 Jun 2025 13:06:25 +0000</pubDate>
      <link>https://dev.to/mohsinsheikhani/hands-on-with-amazon-bedrock-agents-hotel-booking-assistant-with-action-groups-and-knowledge-bases-1j</link>
      <guid>https://dev.to/mohsinsheikhani/hands-on-with-amazon-bedrock-agents-hotel-booking-assistant-with-action-groups-and-knowledge-bases-1j</guid>
      <description>&lt;p&gt;In this guide, I’ll walk through how I built a hotel room booking assistant using Amazon Bedrock Agents and AWS Lambda, combining large language models with real business logic.&lt;/p&gt;

&lt;p&gt;The goal? Let customers ask questions about hotel rooms, check availability, and book a room, all through natural conversation.&lt;/p&gt;

&lt;p&gt;This isn’t just another chatbot. Behind the scenes, it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pulls room descriptions from a knowledge base (S3 PDF)&lt;/li&gt;
&lt;li&gt;Checks real-time room availability via DynamoDB&lt;/li&gt;
&lt;li&gt;Books reservations using a serverless API&lt;/li&gt;
&lt;li&gt;And coordinates all of this using Bedrock Agents + Action Groups&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why this project matters
&lt;/h2&gt;

&lt;p&gt;If you're working with hotels, resorts, or any customer-facing business, conversational interfaces are becoming more than a nice-to-have, they're a competitive advantage. Instead of just answering FAQs, this agent can actually act: check, query, and write to your backend systems.&lt;/p&gt;

&lt;p&gt;This project is meant to show exactly how that’s possible, and how far Bedrock Agents have come.&lt;/p&gt;

&lt;h2&gt;
  
  
  We’ll create:
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsuu81b12dhj64twrkvxg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsuu81b12dhj64twrkvxg.png" alt="Hotel Booking Amazon Bedrock Agent Architecture" width="800" height="484"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An Amazon Bedrock Agent powered by Claude 3.5 Sonnet&lt;/li&gt;
&lt;li&gt;A Knowledge Base (PDF stored in S3) describing room types&lt;/li&gt;
&lt;li&gt;Two Action Groups (backed by Lambda + OpenAPI):

&lt;ul&gt;
&lt;li&gt;One to check room availability&lt;/li&gt;
&lt;li&gt;Another to book a reservation&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;A couple of DynamoDB tables for storing room and booking data&lt;/li&gt;

&lt;li&gt;And a simple walkthrough to connect it all together&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Let’s get into the build.
&lt;/h2&gt;

&lt;p&gt;Head over to the Amazon Bedrock console:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftclvoosj8wsfszcqs57i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftclvoosj8wsfszcqs57i.png" alt="Amazon Bedrock Agent Console" width="800" height="241"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Start by creating a new agent, give it a name and a short description , like so:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9yl5xn3kovxfes9h3fgs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9yl5xn3kovxfes9h3fgs.png" alt="Creating an Amazon Bedrock Agent" width="620" height="484"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the agent is created, it's time to choose the foundation model it will use to generate responses. Click on &lt;code&gt;Select Model&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm9m665pz0fb8r4dof10z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm9m665pz0fb8r4dof10z.png" alt="Agent Builder" width="800" height="364"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Choose &lt;code&gt;Anthropic&lt;/code&gt; as the provider and select the &lt;code&gt;Claude 3.5 Sonnet&lt;/code&gt; model, then click Apply.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you don’t have access to this model yet, go to Model Access in the Bedrock console and request access for Claude 3.5 Sonnet.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fotb8ztsvm7ua8yir1yau.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fotb8ztsvm7ua8yir1yau.png" alt="Claude 3.5 Sonnet model selection" width="800" height="694"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You should now see the selected model listed in the Agent Builder:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6vza3kv6b8ynff5t7ck7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6vza3kv6b8ynff5t7ck7.png" alt="Agent Builder" width="800" height="379"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, time for us to fill the &lt;code&gt;Instruction for Agent&lt;/code&gt; input box. Scroll down to the Instruction for Agent section. copy the instruction from this &lt;a href="https://github.com/mohsinsheikhani/bedrock-hotel-agent/blob/main/resources/AgentInstructions.txt" rel="noopener noreferrer"&gt;GitHub Link&lt;/a&gt;, and paste it into the instruction input box:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frwqg3pvkso96d3myshl2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frwqg3pvkso96d3myshl2.png" alt="Instruction for Amazon Bedrock Agent" width="800" height="545"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, expand &lt;code&gt;Additional Settings&lt;/code&gt; and make sure &lt;code&gt;User Input&lt;/code&gt; is enabled, this way the agent can ask clarification question from user to make a correct decision when needed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyajeli3gj378j2yzwbnj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyajeli3gj378j2yzwbnj.png" alt="User Input enable for Amazon Bedrock Agent" width="800" height="189"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now scroll back to the top, click &lt;code&gt;Save&lt;/code&gt;, and then &lt;code&gt;Prepare&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fki8uz6gco5ehfnu2p5nr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fki8uz6gco5ehfnu2p5nr.png" alt="Edit in Agent Builder" width="800" height="228"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Time for a quick test:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1soladi5vmbawrj1emxh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1soladi5vmbawrj1emxh.png" alt="Initial test for Amazon Bedrock Agent" width="686" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice how the agent uses the instructions to guide its answers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Clone the CDK Repository
&lt;/h3&gt;

&lt;p&gt;Now let’s set up the backend infrastructure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone https://github.com/mohsinsheikhani/bedrock-hotel-agent
cd bedrock-hotel-agent
npm install
cdk deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will provision:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;2 Lambda functions (check &amp;amp; book availability)&lt;/li&gt;
&lt;li&gt;2 DynamoDB tables&lt;/li&gt;
&lt;li&gt;1 S3 bucket (to store the knowledge base PDF)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To add sample room availability to the &lt;code&gt;HotelRoomAvailabilityTable&lt;/code&gt;, run the following scriptL&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python3 ./scripts/insert-to-room-availability.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To verify, go to the DynamoDB Console, open the &lt;code&gt;HotelRoomAvailabilityTable&lt;/code&gt;, and click Explore Items:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffbxh5nflikojl9s3wnwy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffbxh5nflikojl9s3wnwy.png" alt="Explore Items on HotelRoomAvailabilityTable DynamoDB Table" width="529" height="408"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Add Domain Knowledge Using Bedrock Knowledge Base
&lt;/h3&gt;

&lt;p&gt;Right now, the agent knows how to help, thanks to the instructions we gave it, but it still doesn’t know what types of rooms exist or what amenities each offers.&lt;/p&gt;

&lt;p&gt;Let’s fix that.&lt;/p&gt;

&lt;p&gt;We’ll give the agent real hotel knowledge using Amazon Bedrock Knowledge Bases, backed by an S3-hosted PDF.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is where Retrieval-Augmented Generation (RAG) comes into play. Instead of stuffing everything into the prompt, the model can now pull specific answers directly from documents, structured or unstructured, at runtime.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Start by uploading the &lt;code&gt;Hilton-Portfolio.pdf&lt;/code&gt; file (&lt;a href="https://github.com/mohsinsheikhani/bedrock-hotel-agent/blob/main/resources/Hilton-Portfolio.pdf" rel="noopener noreferrer"&gt;included in the repo&lt;/a&gt;) to the S3 bucket we provisioned with CDK: &lt;code&gt;agent-kb-assets&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwklka8judbc3831470o0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwklka8judbc3831470o0.png" alt="Uploaded file on S3 bucket for Amazon Bedrock Knowledge Bases" width="800" height="167"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, we’ll create a knowledge base and wire it to our agent so it can pull context from this PDF when answering questions.&lt;/p&gt;

&lt;p&gt;Head to the Amazon Bedrock Console, Look for &lt;code&gt;Knowledge Bases&lt;/code&gt; underneath &lt;code&gt;Builder tools&lt;/code&gt;, click on it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2qavj5maf5dyn9c6caxr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2qavj5maf5dyn9c6caxr.png" alt="Knowledge Bases on Amazon Bedrock console" width="800" height="341"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;code&gt;Create&lt;/code&gt; and choose the &lt;code&gt;Knowledge base with vector store&lt;/code&gt; option:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F36ct7e4a1y0nq5c220jv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F36ct7e4a1y0nq5c220jv.png" alt="Knowledge Base with vector store, Amazon Bedrock Agent" width="800" height="402"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fill up the Knowledge Base details with a name, and choose S3 as the Data Source&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm76hj9x227hdab8ssj8j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm76hj9x227hdab8ssj8j.png" alt="Knowledge Base Details" width="800" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next step, is to configure the Data Source and point to the S3 path where your PDF lives:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6vvkltnnk4046x4feokt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6vvkltnnk4046x4feokt.png" alt="Configure Knowledge Base Data Source for Amazon Bedrock Agent" width="800" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For Embeddings, select &lt;code&gt;Amazon Titan&lt;/code&gt;. For Vector Store, choose &lt;code&gt;Amazon OpenSearch Serverless&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This setup gives you serverless RAG with native AWS services:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fszxbhxc9bxuvth7fxjnw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fszxbhxc9bxuvth7fxjnw.png" alt="Configure data storage and processing, using Amazon Titan as the embedding model and Amazon OpenSearch Serverless as the vector data store" width="800" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click Next, review your configuration, then click &lt;code&gt;Create&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0igjiutdkhqpqgd6a9td.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0igjiutdkhqpqgd6a9td.png" alt="Amazon Bedrock Knowledge Base creation in-progress" width="800" height="200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once it’s created, open your Knowledge Base and click &lt;code&gt;Sync&lt;/code&gt; to begin parsing the document and storing the chunks for semantic search:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8ohwl83s5wc1vrsmzjuj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8ohwl83s5wc1vrsmzjuj.png" alt="Syncing Knowledge Base with Amazon S3 Data source" width="800" height="288"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Return to the &lt;code&gt;Agent Builder&lt;/code&gt;, scroll down to the &lt;code&gt;Knowledge bases&lt;/code&gt; section:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvczo1kpoojxzz1sumwt3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvczo1kpoojxzz1sumwt3.png" alt="Knowledge Bases, Agent Builder" width="800" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click Add knowledge base, select the one you just created, and fill in the Instruction box with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;As an agent route any question by the user related to room type, room amenities, room description, hotel location to the knowledge bases.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5cjvxvqhle98e4qothbd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5cjvxvqhle98e4qothbd.png" alt="Add Knowledge Base" width="800" height="309"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;code&gt;Add&lt;/code&gt;, and you’ll see it appear in the list:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F737exxyx8nhlhh9hxq86.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F737exxyx8nhlhh9hxq86.png" alt="Added Knowledge Base to Amazon Bedrock Agent" width="800" height="141"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then, click Save, and Prepare your agent:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fki8uz6gco5ehfnu2p5nr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fki8uz6gco5ehfnu2p5nr.png" alt="Edit in Agent Builder" width="800" height="228"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now give the agent a prompt like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;What amenities are included in Embassy Suites by Hilton?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response should include real answers from the document you uploaded.&lt;br&gt;
But what’s even cooler?&lt;br&gt;
Scroll down and open the Orchestration trace. You’ll see the agent actively calling the Knowledge Base behind the scenes:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F02wc6l7km56h1tgsqsaz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F02wc6l7km56h1tgsqsaz.png" alt="Orchestration and Knowledge Base" width="800" height="720"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This confirms our RAG setup is working, the agent is now retrieval-aware and can ground its responses in real business content.&lt;/p&gt;
&lt;h4&gt;
  
  
  Clean Up the Knowledge Base (To Avoid Charges)
&lt;/h4&gt;

&lt;p&gt;Amazon Bedrock Knowledge Bases can incur ongoing charges, especially due to the underlying OpenSearch collection.&lt;br&gt;
At this point, if you're just experimenting or done testing:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go back to Agent Builder and remove the Knowledge Base from your agent.&lt;/li&gt;
&lt;li&gt;Then, head to Builder Tools → Knowledge Bases and delete the Knowledge Base itself.&lt;/li&gt;
&lt;li&gt;Finally, check the OpenSearch Service dashboard. If there's a collection still running, delete it too.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This will ensure you’re not billed unnecessarily going forward.&lt;/p&gt;
&lt;h3&gt;
  
  
  Action Group 01 - Room Availability Checks
&lt;/h3&gt;

&lt;p&gt;Now let’s move beyond answering questions and into actions.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Action Groups are just backend utilities which the Bedrock Agent uses to call external code on our behalf.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We'll start with a simple but critical one:&lt;br&gt;
Checking if a specific room is available for a customer’s requested dates.&lt;/p&gt;

&lt;p&gt;Go to your Agent Builder and scroll to Action groups.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftdqkr6zzz4700s1jje6e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftdqkr6zzz4700s1jje6e.png" alt="Action Group, Amazon Bedrock Agent" width="800" height="186"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click Add, give your Action Group a name, and configure it as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Action group type: Define with API schema&lt;/li&gt;
&lt;li&gt;Lambda function: Select the existing one: &lt;code&gt;RoomAvailabilityHandler&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faitg8jmvmj3q57iaqk7r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faitg8jmvmj3q57iaqk7r.png" alt="Create Action Group, Amazon Bedrock Agent" width="800" height="482"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the API Schema, choose &lt;code&gt;Define via in-line schema editor&lt;/code&gt;.&lt;br&gt;
Then paste the OpenAPI schema from this &lt;a href="https://github.com/mohsinsheikhani/bedrock-hotel-agent/blob/main/resources/HotelRoomAvailability_OpenAPISchema.yaml" rel="noopener noreferrer"&gt;GitHub Link&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg3k2kqkdt8abcxix3rrw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg3k2kqkdt8abcxix3rrw.png" alt="Action Group Schema, Amazon Bedrock Agent" width="800" height="482"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click Create, and you’ll see it appear in your Action Groups list:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0meskcs77ej7jdszbigt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0meskcs77ej7jdszbigt.png" alt="Action Group List, Amazon Bedrock Agent" width="800" height="138"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Give the Agent Permission to Call the Lambda
&lt;/h4&gt;

&lt;p&gt;By default, the agent can't invoke your Lambda function, we need to explicitly allow it.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to the &lt;code&gt;RoomAvailabilityHandler&lt;/code&gt; function in Lambda console.&lt;/li&gt;
&lt;li&gt;In the Configuration tab, scroll to Permissions &amp;gt; Resource-based policy statements&lt;/li&gt;
&lt;li&gt;Click Add permissions&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5fxkom10cbn8emwjvtc7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5fxkom10cbn8emwjvtc7.png" alt=" " width="800" height="411"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fill it out like so:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2ra3xt103x911x5e3muc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2ra3xt103x911x5e3muc.png" alt="Assigning Permissions to Amazon Bedrock Agents to invoke a lambda function" width="800" height="411"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Head back to Agent Builder, and click Save and then Prepare:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fki8uz6gco5ehfnu2p5nr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fki8uz6gco5ehfnu2p5nr.png" alt="Edit in Agent Builder" width="800" height="228"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Try testing it out:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Can you check the room availability for 2025-12-25?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You’ll see the agent reason through the prompt and call your Lambda:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0n44xmplcywqtav6v6wr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0n44xmplcywqtav6v6wr.png" alt="Testing Amazon Bedrock Agent" width="707" height="381"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And in the tracing panel, you'll notice that the agent invoked your Action Group exactly when it needed to:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Facfzzwfukdf96t5egrmk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Facfzzwfukdf96t5egrmk.png" alt="Orchestration and Knowledge Base" width="800" height="742"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That wraps up our first Action Group i.e. Room availability.&lt;/p&gt;

&lt;h3&gt;
  
  
  Action Group 02 - Room Booking
&lt;/h3&gt;

&lt;p&gt;Now that our agent can check availability, it’s time to let it book a room when the user is ready.&lt;/p&gt;

&lt;p&gt;We’ll create a second Action Group that invokes a Lambda function to store booking details in DynamoDB.&lt;/p&gt;

&lt;p&gt;Create another Action Group choosing the same steps i.e.:&lt;/p&gt;

&lt;p&gt;Just like before, go to Agent Builder &amp;gt; Action groups and click Add.&lt;/p&gt;

&lt;p&gt;Configure it with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Action group type: Define with API schema&lt;/li&gt;
&lt;li&gt;Lambda function: &lt;code&gt;HotelRoomBookingHandler&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;API Schema: Select in-line schema editor and paste the OpenAPI schema from this &lt;a href="https://github.com/mohsinsheikhani/bedrock-hotel-agent/blob/main/resources/HotelRoomBooking_OpenAPISchema.yaml" rel="noopener noreferrer"&gt;GitHub Link&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5pe0ltzdab64o1uxwwo7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5pe0ltzdab64o1uxwwo7.png" alt="Action Group Schema, Amazon Bedrock Agent" width="800" height="373"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2crw04kqdngoq6iqrhgp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2crw04kqdngoq6iqrhgp.png" alt="Action Group Schema, Amazon Bedrock Agent" width="800" height="566"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click Create, and you’ll see it appear in your Action Groups list:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fesjsb9aqme300lr26dpe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fesjsb9aqme300lr26dpe.png" alt="Action Group List, Amazon Bedrock Agent" width="800" height="183"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Give the Agent Permission to Call the Lambda
&lt;/h4&gt;

&lt;p&gt;Your agent can’t call this function until you give it explicit permission.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to the &lt;code&gt;HotelRoomBookingHandler&lt;/code&gt; function in Lambda console.&lt;/li&gt;
&lt;li&gt;In the Configuration tab, scroll to Permissions &amp;gt; Resource-based policy statements&lt;/li&gt;
&lt;li&gt;Click Add permissions&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fofiu5vlwyy9z4xtb89lc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fofiu5vlwyy9z4xtb89lc.png" alt="Lambda Resource-based policy statements" width="800" height="417"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6azobavz764vj9m04fee.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6azobavz764vj9m04fee.png" alt="Lambda Resource-based policy statements" width="800" height="177"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Use the same settings as shown:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnanm813zdu2pdm0z7j1y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnanm813zdu2pdm0z7j1y.png" alt="Assigning Permissions to Amazon Bedrock Agents to invoke a lambda function" width="800" height="370"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmksd00523kthxo49m8g2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmksd00523kthxo49m8g2.png" alt="Lambda Resource-based policy statements" width="800" height="140"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Back in Agent Builder, click Save and then Prepare to update the agent.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test the Full Booking Flow
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1tlrkrldnpy9naa9fz7r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1tlrkrldnpy9naa9fz7r.png" alt="Amazon Bedrock Agents Demo" width="667" height="652"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkr4v9prb52463vu4l4af.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkr4v9prb52463vu4l4af.png" alt="Amazon Bedrock Agents Demo" width="662" height="618"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fae8y4flskl2nuaknb7m1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fae8y4flskl2nuaknb7m1.png" alt="Amazon Bedrock Agents Demo" width="662" height="618"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the Booking is successful, verify it by going to DynamoDB, click on Explore Items and choose the &lt;code&gt;HotelRoomBookingTable&lt;/code&gt; and you should see the entry created via the Bedrock Agent.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1bl4myjaowuds7zmw8rg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1bl4myjaowuds7zmw8rg.png" alt="DynamoDB table for booking made by Amazon Bedrock Agent" width="800" height="383"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And that's it, you’ve now wired up an LLM agent that can check room availability and book hotel stays, powered by real APIs.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>bedrockagents</category>
      <category>agentaichallenge</category>
    </item>
    <item>
      <title>A Practical Guide to MLOps on AWS: Transforming Raw Data into AI-Ready Datasets with AWS Glue (Phase 02)</title>
      <dc:creator>Mohsin Sheikhani</dc:creator>
      <pubDate>Thu, 12 Jun 2025 11:36:42 +0000</pubDate>
      <link>https://dev.to/mohsinsheikhani/a-practical-guide-to-mlops-on-aws-transforming-raw-data-into-ai-ready-datasets-with-aws-glue-4lc9</link>
      <guid>https://dev.to/mohsinsheikhani/a-practical-guide-to-mlops-on-aws-transforming-raw-data-into-ai-ready-datasets-with-aws-glue-4lc9</guid>
      <description>&lt;p&gt;In &lt;a href="https://dev.to/mohsinsheikhani/a-practical-guide-to-mlops-on-aws-streaming-data-ingestion-with-kinesis-firehose-phase-01-1bi7"&gt;Phase 01&lt;/a&gt;, we built the ingestion layer of our Retail AI Insights system. We streamed historical product interaction data into Amazon S3 (Bronze zone) and stored key product metadata with inventory information in DynamoDB.&lt;/p&gt;

&lt;p&gt;Now that we have raw data arriving reliably, it's time to clean, enrich, and organize it for downstream AI workflows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Objective
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Transform raw event data from the Bronze zone into:&lt;/li&gt;
&lt;li&gt;Cleaned, analysis-ready Parquet files in the Silver zone&lt;/li&gt;
&lt;li&gt;Forecast-specific feature sets in the Gold zone under &lt;code&gt;/forecast_ready/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Recommendation-ready CSV files under &lt;code&gt;/recommendations_ready/&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpvcmvkoqz04t60li98az.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpvcmvkoqz04t60li98az.png" alt="Transforming Raw Data into AI-Ready Datasets with AWS Glue Architecture Diagram" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This will power:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Demand forecasting via Amazon Bedrock&lt;/li&gt;
&lt;li&gt;Personalized product recommendations using Amazon Personalize&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What We'll Build in This Phase
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;AWS Glue Jobs: Python scripts to clean, transform, and write data to the appropriate S3 zone&lt;/li&gt;
&lt;li&gt;AWS Glue Crawlers: Catalog metadata from S3 into tables for Athena &amp;amp; further processing&lt;/li&gt;
&lt;li&gt;AWS CDK Stack: Provisions all jobs, buckets, and crawlers&lt;/li&gt;
&lt;li&gt;Athena Queries: Run sanity checks on the transformed data&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Directory &amp;amp; Bucket Layout
&lt;/h2&gt;

&lt;p&gt;We'll now be working with the following S3 zones:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;retail-ai-bronze-zone/&lt;/code&gt; → Raw JSON from Firehose&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;retail-ai-silver-zone/cleaned_data/&lt;/code&gt; → Cleaned Parquet&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;retail-ai-gold-zone/forecast_ready/&lt;/code&gt; → Aggregated features for forecasting&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;retail-ai-gold-zone/recommendations_ready/&lt;/code&gt; → CSV with item metadata for Personalize&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You'll also notice a fourth bucket: &lt;code&gt;retail-ai-zone-assets/&lt;/code&gt;, this stores scripts, and training dataset.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 - Creating Glue Resources via CDK
&lt;/h2&gt;

&lt;p&gt;Now that we've set up our storage zones and uploaded the required ETL scripts and datasets, it's time to define the Glue resources with AWS CDK.&lt;/p&gt;

&lt;p&gt;We'll create:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;3 Glue Jobs

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;DataCleaningETLJob&lt;/strong&gt; → Cleans raw JSON into structured Parquet for the Silver Zone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ForecastGoldETLJob&lt;/strong&gt; → Transforms cleaned data with features for demand prediction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RecommendationGoldETLJob&lt;/strong&gt; → Prepares item metadata CSV for Amazon Personalize.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Four Crawlers&lt;/li&gt;

&lt;li&gt;Validate everything with Athena&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;From the project root, generate the construct file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir -p lib/constructs/analytics &amp;amp;&amp;amp; touch lib/constructs/analytics/glue-resources.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure your local scripts/ and dataset/ directories are present, then upload them to your S3 assets bucket:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aws s3 cp ./scripts/sales_etl_script.py s3://retail-ai-zone-assets/scripts/
aws s3 cp ./scripts/forecast_gold_etl_script.py s3://retail-ai-zone-assets/scripts/
aws s3 cp ./scripts/user_interaction_etl_script.py s3://retail-ai-zone-assets/scripts/
aws s3 cp ./dataset/events_with_metadata.csv s3://retail-ai-zone-assets/dataset/
aws s3 cp ./scripts/inventory_forecaster.py s3://retail-ai-zone-assets/scripts/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Define Glue Jobs &amp;amp; Crawlers in CDK
&lt;/h3&gt;

&lt;p&gt;Now, open the &lt;code&gt;lib/constructs/analytics/glue-resources.ts&lt;/code&gt; file and define the full CDK logic to create:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Glue job role with required permissions&lt;/li&gt;
&lt;li&gt;The three ETL jobs with their respective scripts&lt;/li&gt;
&lt;li&gt;Four crawlers with S3 targets pointing to Bronze, Silver, Forecast, and Recommendation zones&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Open the &lt;code&gt;lib/constructs/analytics/glue-resources.ts&lt;/code&gt; file, and add the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Construct } from "constructs";
import * as cdk from "aws-cdk-lib";

import { Bucket } from "aws-cdk-lib/aws-s3";
import { CfnCrawler, CfnJob, CfnDatabase } from "aws-cdk-lib/aws-glue";
import {
  Role,
  ServicePrincipal,
  ManagedPolicy,
  PolicyStatement,
} from "aws-cdk-lib/aws-iam";

interface GlueProps {
  bronzeBucket: Bucket;
  silverBucket: Bucket;
  goldBucket: Bucket;
  dataAssetsBucket: Bucket;
}

export class GlueResources extends Construct {
  constructor(scope: Construct, id: string, props: GlueProps) {
    super(scope, id);

    const { bronzeBucket, silverBucket, goldBucket, dataAssetsBucket } = props;

    // Glue Database
    const glueDatabase = new CfnDatabase(this, "SalesDatabase", {
      catalogId: cdk.Stack.of(this).account,
      databaseInput: {
        name: "sales_data_db",
      },
    });

    // Create IAM Role for Glue
    const glueRole = new Role(this, "GlueServiceRole", {
      assumedBy: new ServicePrincipal("glue.amazonaws.com"),
    });

    bronzeBucket.grantRead(glueRole);
    silverBucket.grantReadWrite(glueRole);
    goldBucket.grantReadWrite(glueRole);

    glueRole.addToPolicy(
      new PolicyStatement({
        actions: ["s3:GetObject"],
        resources: [`${dataAssetsBucket.bucketArn}/*`],
      })
    );

    glueRole.addManagedPolicy(
      ManagedPolicy.fromAwsManagedPolicyName("service-role/AWSGlueServiceRole")
    );

    // Glue Crawler (for Bronze Bucket)
    new CfnCrawler(this, "DataCrawlerBronze", {
      name: "DataCrawlerBronze",
      role: glueRole.roleArn,
      databaseName: glueDatabase.ref,
      targets: {
        s3Targets: [{ path: bronzeBucket.s3UrlForObject() }],
      },
      tablePrefix: "bronze_",
    });

    // Glue ETL Job
    new CfnJob(this, "DataCleaningETLJob", {
      name: "DataCleaningETLJob",
      role: glueRole.roleArn,
      command: {
        name: "glueetl",
        pythonVersion: "3",
        scriptLocation: dataAssetsBucket.s3UrlForObject(
          "scripts/sales_etl_script.py"
        ),
      },
      defaultArguments: {
        "--TempDir": silverBucket.s3UrlForObject("temp/"),
        "--job-language": "python",
        "--bronze_bucket": bronzeBucket.bucketName,
        "--silver_bucket": silverBucket.bucketName,
      },
      glueVersion: "3.0",
      maxRetries: 0,
      timeout: 10,
      workerType: "Standard",
      numberOfWorkers: 2,
    });

    // Glue Crawler (for Silver Bucket)
    new CfnCrawler(this, "DataCrawlerSilver", {
      name: "DataCrawlerSilver",
      role: glueRole.roleArn,
      databaseName: glueDatabase.ref,
      targets: {
        s3Targets: [
          {
            path: `${silverBucket.s3UrlForObject()}/cleaned_data/`,
          },
        ],
      },
      tablePrefix: "silver_",
    });

    // Glue Crawler (for Gold Bucket)
    new CfnCrawler(this, "DataCrawlerForecast", {
      name: "DataCrawlerForecast",
      role: glueRole.roleArn,
      databaseName: glueDatabase.ref,
      targets: {
        s3Targets: [{ path: `${goldBucket.s3UrlForObject()}/forecast_ready/` }],
      },
      tablePrefix: "gold_",
    });

    // Glue Crawler (for Gold Bucket)
    new CfnCrawler(this, "DataCrawlerRecommendations", {
      name: "DataCrawlerRecommendations",
      role: glueRole.roleArn,
      databaseName: glueDatabase.ref,
      targets: {
        s3Targets: [
          { path: `${goldBucket.s3UrlForObject()}/recommendations_ready/` },
        ],
      },
      tablePrefix: "gold_",
    });

    // Glue ETL Job to output forecast ready dataset
    new CfnJob(this, "ForecastGoldETLJob", {
      name: "ForecastGoldETLJob",
      role: glueRole.roleArn,
      command: {
        name: "glueetl",
        pythonVersion: "3",
        scriptLocation: dataAssetsBucket.s3UrlForObject(
          "scripts/forecast_gold_etl_script.py"
        ),
      },
      defaultArguments: {
        "--TempDir": silverBucket.s3UrlForObject("temp/"),
        "--job-language": "python",
        "--silver_bucket": silverBucket.bucketName,
        "--gold_bucket": goldBucket.bucketName,
      },
      glueVersion: "3.0",
      maxRetries: 0,
      timeout: 10,
      workerType: "Standard",
      numberOfWorkers: 2,
    });

    // Glue ETL Job to output recommendation ready dataset
    new CfnJob(this, "RecommendationGoldETLJob", {
      name: "RecommendationGoldETLJob",
      role: glueRole.roleArn,
      command: {
        name: "glueetl",
        pythonVersion: "3",
        scriptLocation: dataAssetsBucket.s3UrlForObject(
          "scripts/user_interaction_etl_script.py"
        ),
      },
      defaultArguments: {
        "--TempDir": silverBucket.s3UrlForObject("temp/"),
        "--job-language": "python",
        "--silver_bucket": silverBucket.bucketName,
        "--gold_bucket": goldBucket.bucketName,
      },
      glueVersion: "3.0",
      maxRetries: 0,
      timeout: 10,
      workerType: "Standard",
      numberOfWorkers: 2,
    });
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wire it up on the &lt;code&gt;retail-ai-insights-stack.ts&lt;/code&gt; file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/**
 * Glue ETL Resources
 **/
new GlueResources(this, "GlueResources", {
  bronzeBucket,
  silverBucket,
  goldBucket,
  dataAssetsBucket,
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once deployed via &lt;code&gt;cdk deploy&lt;/code&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Navigate to AWS Glue &amp;gt; ETL Jobs - You should see:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgcviikq2qlizdk6vul2u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgcviikq2qlizdk6vul2u.png" alt="AWS Glue Studio" width="800" height="266"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to AWS Glue &amp;gt; Data Catalog &amp;gt; Crawlers – Ensure four crawlers exist:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwntiecqwoefxdjt7bwjw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwntiecqwoefxdjt7bwjw.png" alt="AWS Glue Crawlers" width="800" height="193"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2 - Run Glue Jobs to Transform Raw Data
&lt;/h3&gt;

&lt;p&gt;Now that our Glue jobs and crawlers are deployed, let’s walk through how we run the ETL flow across the Bronze, Silver, and Gold zones.&lt;/p&gt;

&lt;h4&gt;
  
  
  Locate Raw Data in Bronze Bucket
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;Go to the Amazon S3 Console, open the &lt;code&gt;retail-ai-bronze-zone bucket&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Drill down through the directories until you see the file, note the tree structure, in my case it's &lt;code&gt;dataset/2025/05/26/20&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Copy this full prefix path.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Update the ETL Script Input Path
&lt;/h4&gt;

&lt;p&gt;Open the &lt;code&gt;sales_etl_script.py&lt;/code&gt; inside VSCode.&lt;br&gt;
On line 36, update the input_path variable to reflect the directory path you just copied:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;input_path = f"s3://{bronze_bucket}/dataset/2025/05/26/20/"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Re-upload the modified script to your S3 data-assets bucket:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aws s3 cp ./scripts/sales_etl_script.py s3://retail-ai-zone-assets/scripts/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because versioning is enabled on the bucket, this will replace the previous file while preserving version history.&lt;/p&gt;

&lt;h4&gt;
  
  
  Run the ETL Jobs
&lt;/h4&gt;

&lt;p&gt;Now let’s kick off the transformation pipeline:&lt;/p&gt;

&lt;p&gt;Run &lt;code&gt;DataCleaningETLJob&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to AWS Glue Console &amp;gt; ETL Jobs.&lt;/li&gt;
&lt;li&gt;Select the &lt;code&gt;DataCleaningETLJob&lt;/code&gt; and click Run Job.&lt;/li&gt;
&lt;li&gt;This job will:

&lt;ul&gt;
&lt;li&gt;Read raw JSON data from the Bronze bucket.&lt;/li&gt;
&lt;li&gt;Clean, cast, and convert it to Parquet.&lt;/li&gt;
&lt;li&gt;Store the results in the &lt;code&gt;retail-ai-silver-zone&lt;/code&gt; bucket under &lt;code&gt;cleaned_data/&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F214xah13td2wnigk6hp2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F214xah13td2wnigk6hp2.png" alt="Running AWS Glue Job" width="800" height="151"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once successful, navigate to the &lt;code&gt;retail-ai-silver-zone&lt;/code&gt; bucket and confirm:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa7f7ksmgz4thzmvnu3sg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa7f7ksmgz4thzmvnu3sg.png" alt="S3 Bucket for Silver Zone" width="800" height="327"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Run &lt;code&gt;ForecastGoldETLJob&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to AWS Glue Console &amp;gt; ETL Jobs.&lt;/li&gt;
&lt;li&gt;Select the &lt;code&gt;ForecastGoldETLJob&lt;/code&gt; and click Run Job.&lt;/li&gt;
&lt;li&gt;This job will:

&lt;ul&gt;
&lt;li&gt;Read the cleaned data from &lt;code&gt;retail-ai-silver-zone/cleaned_data/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Aggregate daily sales&lt;/li&gt;
&lt;li&gt;Output the transformed data to &lt;code&gt;retail-ai-gold-zone/forecast_ready/&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcts5otxav3gnk2x4i2u2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcts5otxav3gnk2x4i2u2.png" alt="Running AWS Glue Job" width="800" height="153"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once completed, visit the Gold bucket and confirm the forecast files are present in that directory.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flecq3inlpp3c7413tt6u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flecq3inlpp3c7413tt6u.png" alt="S3 Bucket for Gold Zone" width="800" height="327"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Run &lt;code&gt;RecommendationGoldETLJob&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to AWS Glue Console &amp;gt; ETL Jobs.&lt;/li&gt;
&lt;li&gt;Select the &lt;code&gt;RecommendationGoldETLJob&lt;/code&gt; and click Run Job.&lt;/li&gt;
&lt;li&gt;This job will:

&lt;ul&gt;
&lt;li&gt;Read cleaned product data from the Silver zone&lt;/li&gt;
&lt;li&gt;Output only the required item metadata in CSV format&lt;/li&gt;
&lt;li&gt;Save to &lt;code&gt;retail-ai-gold-zone/recommendations_ready/&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk0y556rnah8jkdqg94ya.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk0y556rnah8jkdqg94ya.png" alt="Running AWS Glue Job" width="800" height="147"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After the job runs successfully, go to the Gold bucket and verify the structure and CSV file.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdk94dhj62uvj4ph5c1k5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdk94dhj62uvj4ph5c1k5.png" alt="S3 Bucket for Gold Zone" width="800" height="184"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Run All Glue Crawlers
&lt;/h3&gt;

&lt;p&gt;Once the Glue crawlers are deployed, you’ll see four of them listed in the Glue Console &amp;gt; Data Catalog &amp;gt; Crawlers:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftcg3is7rhqu9sfwwou6l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftcg3is7rhqu9sfwwou6l.png" alt="AWS Glue Crawlers" width="800" height="193"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Select all four crawlers.&lt;/li&gt;
&lt;li&gt;Click Run.&lt;/li&gt;
&lt;li&gt;Once completed, look at the "Table changes on the last run" column  each should say "1 created".&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2vz0bys6gie08m6hmykd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2vz0bys6gie08m6hmykd.png" alt="AWS Glue Crawlers" width="800" height="188"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Validate Table Creation
&lt;/h4&gt;

&lt;p&gt;Navigate to Glue Console &amp;gt; Data Catalog &amp;gt; Databases &amp;gt; Tables. You should now see four new tables, each corresponding to a specific zone:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqzc3a1xqyuvn466xk748.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqzc3a1xqyuvn466xk748.png" alt="AWS Glue Data Catalog Tables" width="800" height="192"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each table has an automatically inferred schema, including columns like &lt;code&gt;user_id&lt;/code&gt;, &lt;code&gt;event_type&lt;/code&gt;, &lt;code&gt;timestamp&lt;/code&gt;, &lt;code&gt;price&lt;/code&gt;, &lt;code&gt;product_name&lt;/code&gt;, and more.&lt;/p&gt;

&lt;h3&gt;
  
  
  Query with Amazon Athena
&lt;/h3&gt;

&lt;p&gt;Now let’s run SQL queries against these tables:&lt;/p&gt;

&lt;p&gt;Open the Amazon Athena Console.&lt;/p&gt;

&lt;p&gt;If it's your first time, you’ll see a pop-up:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhe67mv82kgmzf12pg96g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhe67mv82kgmzf12pg96g.png" alt="AWS Athena, Output bucket configuration" width="800" height="35"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Choose your &lt;code&gt;retail-ai-zone-assets&lt;/code&gt; bucket.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7numhwljm8hrm3krrfb8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7numhwljm8hrm3krrfb8.png" alt="AWS Athena, Output bucket configuration" width="800" height="278"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click Save.&lt;/p&gt;

&lt;h4&gt;
  
  
  Sample Athena Query
&lt;/h4&gt;

&lt;p&gt;In the query editor, trying running simple SQL queries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Select * from sales_data_db.&amp;lt;TABLE_NAME&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Try this query on the &lt;code&gt;bronze_retail_ai_bronze_zone&lt;/code&gt; table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Select * from sales_data_db.bronze_retail_ai_bronze_zone
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fysz517su15r5shxscjif.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fysz517su15r5shxscjif.png" alt="AWS Athena query result" width="800" height="324"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Try this query on the &lt;code&gt;silver_cleaned_data&lt;/code&gt; table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Select * from sales_data_db.silver_cleaned_data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqxhy0vivi5050ugxvx0t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqxhy0vivi5050ugxvx0t.png" alt="AWS Athena query result" width="800" height="324"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Try this query on the &lt;code&gt;gold_forecast_ready&lt;/code&gt; table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Select * from sales_data_db.gold_forecast_ready
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0crx8codkaccaybup1l0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0crx8codkaccaybup1l0.png" alt="AWS Athena query result" width="800" height="324"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Try this query on the &lt;code&gt;gold_recommendations_ready&lt;/code&gt; table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Select * from sales_data_db.gold_recommendations_ready
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdrwgi0cej9p3lmhq6jk3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdrwgi0cej9p3lmhq6jk3.png" alt="AWS Athena query result" width="800" height="324"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What You’ve Just Built
&lt;/h2&gt;

&lt;p&gt;In this phase, you've gone beyond basic ETL. You’ve engineered a production-grade data lake with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multi-zone architecture (Bronze, Silver, Gold)&lt;/li&gt;
&lt;li&gt;Automated ETL pipelines using AWS Glue&lt;/li&gt;
&lt;li&gt;Schema discovery and validation through Crawlers&lt;/li&gt;
&lt;li&gt;Interactive querying via Amazon Athena&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of this was done infrastructure-as-code first using AWS CDK, with clean separation of storage, processing, and access layers, exactly how real-world cloud data platforms are designed.&lt;/p&gt;

&lt;p&gt;But this isn’t just about organizing data. You’re now sitting on a foundation that’s:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI-ready&lt;/li&gt;
&lt;li&gt;Model-friendly&lt;/li&gt;
&lt;li&gt;Cost-efficient&lt;/li&gt;
&lt;li&gt;And built for scale&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What’s Next?
&lt;/h3&gt;

&lt;p&gt;In Phase 3, we’ll unlock this data’s real potential, using Amazon Bedrock to power AI-based demand forecasting, running nightly on an EC2 instance and storing predictions back into our pipeline.&lt;/p&gt;

&lt;p&gt;You’ve built the rails, now it’s time to run intelligence through them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Complete Code for the Second Phase
&lt;/h2&gt;

&lt;p&gt;To view the full code for the second phase, &lt;a href="https://github.com/mohsinsheikhani/retail-ai-insights/tree/main/02-data-preparation" rel="noopener noreferrer"&gt;checkout the repository on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🚀 &lt;strong&gt;Follow me on &lt;a href="https://www.linkedin.com/in/mohsin-sheikhani/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; for more AWS content!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>mlops</category>
      <category>dataengineering</category>
    </item>
    <item>
      <title>A Practical Guide to MLOps on AWS: Streaming Data Ingestion with Kinesis Firehose (Phase 01)</title>
      <dc:creator>Mohsin Sheikhani</dc:creator>
      <pubDate>Sat, 07 Jun 2025 15:25:55 +0000</pubDate>
      <link>https://dev.to/mohsinsheikhani/a-practical-guide-to-mlops-on-aws-streaming-data-ingestion-with-kinesis-firehose-phase-01-1bi7</link>
      <guid>https://dev.to/mohsinsheikhani/a-practical-guide-to-mlops-on-aws-streaming-data-ingestion-with-kinesis-firehose-phase-01-1bi7</guid>
      <description>&lt;h2&gt;
  
  
  The Big Picture: Why This Project Matters
&lt;/h2&gt;

&lt;p&gt;In the age of AI-driven decisions, retail businesses are sitting on a goldmine of customer interaction data, but most are struggling to use it effectively.&lt;/p&gt;

&lt;p&gt;Imagine this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A customer browses your store but leaves without buying.&lt;/li&gt;
&lt;li&gt;You don't know what caught their eye.&lt;/li&gt;
&lt;li&gt;You don’t know what’s likely to sell tomorrow.&lt;/li&gt;
&lt;li&gt;You’re restocking based on gut feel, not data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the reality for many retailers.&lt;/p&gt;

&lt;p&gt;The goal of this project is to build a cloud-native, AI-enhanced retail analytics platform that solves two critical business problems:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What do you think we should stock next? → Predict demand using historical data and forecast which products need restocking.&lt;/li&gt;
&lt;li&gt;What should we recommend? → Use customer behavior to serve personalized product suggestions at runtime.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And we want to achieve this without maintaining complex infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: Real-Time, AI-Driven Retail Intelligence
&lt;/h2&gt;

&lt;p&gt;We'll build a modern data pipeline with these pillars:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data Lake Architecture:&lt;/strong&gt; S3-based Bronze → Silver → Gold zones for raw, cleaned, and model-ready data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI at the Core:&lt;/strong&gt; Bedrock for forecasting, Personalize for recommendations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-Time &amp;amp; Batch:&lt;/strong&gt; Lambda for on-demand actions, EC2 for nightly forecasting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MLOps Foundation:&lt;/strong&gt; Glue for ETL, EventBridge for orchestration, DynamoDB for fast lookup.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdq9p0itcq74iwovji8di.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdq9p0itcq74iwovji8di.png" alt="Cloud-Native AI Project on AWS for Product Forecasting and Product Recommendations" width="800" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is a full-stack, cloud-native project that covers everything from data ingestion to AI inference.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 01: Ingesting Events Like a Real-Time System
&lt;/h2&gt;

&lt;p&gt;Before anything else, we need &lt;strong&gt;data&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In this phase, we simulate a real-time ingestion pipeline by streaming historical customer interaction data into our system using Kinesis Firehose and Python.&lt;/p&gt;

&lt;h3&gt;
  
  
  What We’re Solving in Phase 1
&lt;/h3&gt;

&lt;p&gt;To build useful AI models, we first need user behavior data. This includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Product views&lt;/li&gt;
&lt;li&gt;Add-to-cart actions&lt;/li&gt;
&lt;li&gt;Purchases&lt;/li&gt;
&lt;li&gt;Timestamps and metadata&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But unlike traditional ETL pipelines, we want to simulate a real-time data flow, so that our system behaves like it would in production, even while testing locally.&lt;/p&gt;

&lt;h3&gt;
  
  
  How We’re Building It
&lt;/h3&gt;

&lt;p&gt;Here’s what we’re doing in this phase:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stream JSON events to Kinesis Firehose, which writes them to an S3 Bronze bucket.&lt;/li&gt;
&lt;li&gt;Simultaneously write selected fields to DynamoDB to store product metadata and inventory.&lt;/li&gt;
&lt;li&gt;Set the foundation for AI-driven recommendations and forecasting later on.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F03oot0mxy3z92yinzlr8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F03oot0mxy3z92yinzlr8.png" alt="Data Ingestion with Kinesis Firehose" width="800" height="303"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Let’s Build It
&lt;/h3&gt;

&lt;p&gt;We’ll now walk through building this ingestion pipeline using AWS CDK, starting with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS CDK project setup&lt;/li&gt;
&lt;li&gt;Creating a DynamoDB table to store product info and initial inventory&lt;/li&gt;
&lt;li&gt;Creating the Base VPC&lt;/li&gt;
&lt;li&gt;Creating the S3 Buckets (bronze, silver, gold zones)&lt;/li&gt;
&lt;li&gt;Setting up Kinesis Firehose to deliver raw data to S3&lt;/li&gt;
&lt;li&gt;Writing Python scripts to simulate streaming events and populating DynamoDB&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 1 - Setting Up the AWS CDK Project
&lt;/h3&gt;

&lt;p&gt;To build this infrastructure in a clean, scalable way, we’re using AWS CDK (Cloud Development Kit). It allows us to define cloud infrastructure using familiar programming languages, in our case, TypeScript.&lt;/p&gt;

&lt;p&gt;We’ll organize everything using a modular folder structure that separates shared resources, analytics components, and common storage logic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Initialize the CDK Project
&lt;/h3&gt;

&lt;p&gt;We start by creating and bootstrapping a CDK app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir retail-ai-insights &amp;amp;&amp;amp; cd retail-ai-insights
cdk init app --language=typescript
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a basic CDK project with boilerplate files like &lt;code&gt;cdk.json&lt;/code&gt;, &lt;code&gt;tsconfig.json&lt;/code&gt;, and a &lt;code&gt;lib/&lt;/code&gt; directory to organize stacks.&lt;/p&gt;

&lt;h4&gt;
  
  
  Organize Your Constructs
&lt;/h4&gt;

&lt;p&gt;Let’s build a modular file structure right from the start. Inside the &lt;code&gt;lib/&lt;/code&gt; directory, run the following one by one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd lib/

# Shared Networking (VPC, Subnets, etc.)
mkdir -p constructs/shared/networking &amp;amp;&amp;amp; touch constructs/shared/networking/vpc.ts

# Analytics Pipeline (Firehose, Glue, etc.)
mkdir -p constructs/analytics &amp;amp;&amp;amp; touch constructs/analytics/firehose-stream.ts

# Common Storage (S3 Buckets, DynamoDB)
mkdir -p constructs/common/storage
touch constructs/common/storage/dynamodb-inventory.ts
touch constructs/common/storage/s3-bucket-factory.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once done, your project should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lib/
├── constructs/
│   ├── shared/
│   │   └── networking/
│   │       └── vpc.ts
│   ├── analytics/
│   │   └── firehose-stream.ts
│   └── common/
│       └── storage/
│           ├── dynamodb-inventory.ts
│           └── s3-bucket-factory.ts
└── retail-ai-insights-stack.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2 - Creating a DynamoDB Table for Product Inventory
&lt;/h3&gt;

&lt;p&gt;Now that our CDK project is set up, let’s provision a DynamoDB table that will store:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Product metadata (product_id, product_name, etc.)&lt;/li&gt;
&lt;li&gt;Current stock levels&lt;/li&gt;
&lt;li&gt;Forecasted demand (to be updated later)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This table will power real-time lookups during both recommendation generation and inventory management phases.&lt;/p&gt;

&lt;p&gt;Open &lt;code&gt;lib/constructs/common/storage/dynamodb-inventory.ts&lt;/code&gt; and add the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Construct } from "constructs";

import * as dynamodb from "aws-cdk-lib/aws-dynamodb";
import * as cdk from "aws-cdk-lib";

export class DynamoDBInventory extends Construct {
  public readonly inventoryTable: dynamodb.Table;

  constructor(scope: Construct, id: string) {
    super(scope, id);

    this.inventoryTable = new dynamodb.Table(this, "RetailInventoryTable", {
      tableName: "RetailInventoryTable",
      partitionKey: { name: "product_id", type: dynamodb.AttributeType.STRING },
      billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
      encryption: dynamodb.TableEncryption.AWS_MANAGED,
      removalPolicy: cdk.RemovalPolicy.DESTROY,
    });
  }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now open &lt;code&gt;lib/retail-ai-insights-stack.ts&lt;/code&gt; and use the construct like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/**
* Retail Inventory Table
**/
const dynamoConstruct = new DynamoDBInventory(this, "DynamoDBInventory");
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To see the output, run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cdk deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Navigate to the DynamoDB console&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1tco3fniticjt8jr6zik.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1tco3fniticjt8jr6zik.png" alt="List of DynamoDB" width="800" height="140"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This completes our DynamoDB setup, giving us a real-time-accessible source of truth for product stock, prices, and metadata.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3 - Creating the Base VPC
&lt;/h3&gt;

&lt;p&gt;We’ll keep it minimal and efficient by using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1 Availability Zones&lt;/li&gt;
&lt;li&gt;Public Subnets (for instance bootstrap, like downloading packages)&lt;/li&gt;
&lt;li&gt;Private Subnets with Egress (for EC2 forecasting to access Bedrock or S3 securely)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Open &lt;code&gt;lib/constructs/shared/networking/vpc.ts&lt;/code&gt; and add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Construct } from "constructs";
import { Vpc, SubnetType, NatProvider } from "aws-cdk-lib/aws-ec2";
import { StackProps } from "aws-cdk-lib";

export interface VpcResourceProps extends StackProps {
  maxAzs?: number;
}

export class VpcResource extends Construct {
  public readonly vpc: Vpc;

  constructor(scope: Construct, id: string, props: VpcResourceProps) {
    super(scope, id);

    this.vpc = new Vpc(this, "RetailForecastVpc", {
      vpcName: "RetailAIVPC",
      maxAzs: props.maxAzs ?? 1,
      natGatewayProvider: NatProvider.gateway(),
      natGateways: 0,
      subnetConfiguration: [
        {
          name: "PublicSubnet",
          subnetType: SubnetType.PUBLIC,
          cidrMask: 24,
        },
        {
          name: "PrivateSubnet",
          subnetType: SubnetType.PRIVATE_WITH_EGRESS,
          cidrMask: 24,
        },
      ],
    });
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;lib/retail-ai-insights-stack.ts&lt;/code&gt;, add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/**
* VPC Setup
**/
const { vpc } = new VpcResource(this, "RetailVpc", {});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's go run the&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cdk deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once done, navigate to the VPC console&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuzse80641jbbje0qlql4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuzse80641jbbje0qlql4.png" alt="List of VPC" width="800" height="118"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3n35doxuw9ojbge9e3we.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3n35doxuw9ojbge9e3we.png" alt="List of Subnets within a VPC" width="800" height="133"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9f6eh3vygjqo0btpkg8z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9f6eh3vygjqo0btpkg8z.png" alt="Route tables associated with a Subnet" width="800" height="143"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With this, we now have an isolated network environment to run our compute workloads with secure access to AWS services (via VPC endpoints, later on).&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4 - Creating the Storage Foundation (S3 Buckets)
&lt;/h3&gt;

&lt;p&gt;A production-grade data lake architecture often follows a multi-zone strategy to maintain a clean separation of data states:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Bronze:&lt;/strong&gt;  Raw data as ingested (e.g., event streams)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Silver:&lt;/strong&gt; Cleaned, filtered, and enriched data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gold:&lt;/strong&gt;    Aggregated or transformed data that's ready for ML/AI consumption&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In our case, we'll also include a &lt;code&gt;zone-assets&lt;/code&gt; bucket to store static datasets and job scripts that need to be referenced during ETL jobs.&lt;/p&gt;

&lt;p&gt;We’re creating multiple S3 buckets with similar configurations (like versioning, encryption, SSL-only access, and auto-deletion in dev environments), rather than duplicating logic, we’ll use a factory construct that makes it reusable and DRY.&lt;/p&gt;

&lt;p&gt;Open the file:&lt;br&gt;
&lt;code&gt;lib/constructs/common/storage/s3-bucket-factory.ts&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;And update it with the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Construct } from "constructs";
import {
  Bucket,
  BlockPublicAccess,
  BucketEncryption,
} from "aws-cdk-lib/aws-s3";
import * as cdk from "aws-cdk-lib";

interface CustomS3BucketProps {
  bucketName: string;
}

export class S3BucketFactory extends Construct {
  public readonly bucket: Bucket;

  constructor(scope: Construct, id: string, props: CustomS3BucketProps) {
    super(scope, id);

    const { bucketName } = props;

    this.bucket = new Bucket(this, "S3Bucket", {
      bucketName,
      versioned: true,
      enforceSSL: true,
      autoDeleteObjects: true,
      blockPublicAccess: BlockPublicAccess.BLOCK_ALL,
      encryption: BucketEncryption.S3_MANAGED,
      removalPolicy: cdk.RemovalPolicy.DESTROY,
    });
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now in your &lt;code&gt;retail-ai-insights-stack.ts&lt;/code&gt;, instantiate the factory construct like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/**
* Multi Zone Bucket
**/
const { bucket: bronzeBucket } = new S3BucketFactory(
  this,
  "BronzeDataLakeBucket",
  {
    bucketName: "retail-ai-bronze-zone",
  }
);

const { bucket: silverBucket } = new S3BucketFactory(
  this,
  "SilverDataLakeBucket",
  {
    bucketName: "retail-ai-silver-zone",
  }
);

const { bucket: goldBucket } = new S3BucketFactory(this, "GoldDataBucket", {
  bucketName: "retail-ai-gold-zone",
});

const { bucket: dataAssetsBucket } = new S3BucketFactory(
  this,
  "DataAssets",
  {
    bucketName: "retail-ai-zone-assets",
  }
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, let's do&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cdk deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify the output by going to the S3 Bucket Console, you should see four of the buckets&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8tgvp1ythvnguae9t16a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8tgvp1ythvnguae9t16a.png" alt="General purpose S3 Buckets" width="800" height="202"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This sets up the core storage foundation that the rest of our ETL, forecasting, and recommendation workflows will depend on.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5 - Setting up Kinesis Firehose to deliver raw data to S3
&lt;/h3&gt;

&lt;p&gt;With our Bronze bucket in place, we’re now ready to stream raw event data into it using Amazon Kinesis Data Firehose. Firehose is a fully managed service for delivering real-time streaming data directly to destinations like Amazon S3.&lt;/p&gt;

&lt;p&gt;In our architecture, it enables our ingestion pipeline by capturing event streams and persisting them as raw JSON files in the Bronze zone.&lt;/p&gt;

&lt;p&gt;Oepn the file:&lt;br&gt;
&lt;code&gt;lib/constructs/analytics/firehose-stream.ts&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Add the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Construct } from "constructs";

import { CfnDeliveryStream } from "aws-cdk-lib/aws-kinesisfirehose";
import { PolicyStatement, Role, ServicePrincipal } from "aws-cdk-lib/aws-iam";
import { Bucket } from "aws-cdk-lib/aws-s3";
import { LogGroup, LogStream } from "aws-cdk-lib/aws-logs";

interface FirehoseProps {
  destinationBucket: Bucket;
}

export class FirehoseToS3 extends Construct {
  public readonly deliveryStream: CfnDeliveryStream;

  constructor(scope: Construct, id: string, props: FirehoseProps) {
    super(scope, id);

    const logGroup = new LogGroup(this, "FirehoseLogGroup");
    const logStream = new LogStream(this, "FirehoseLogStream", {
      logGroup,
    });

    // IAM Role for Firehose to access S3
    const firehoseRole = new Role(this, "FirehoseRole", {
      assumedBy: new ServicePrincipal("firehose.amazonaws.com"),
    });

    props.destinationBucket.grantWrite(firehoseRole);

    firehoseRole.addToPolicy(
      new PolicyStatement({
        actions: [
          "logs:PutLogEvents",
          "logs:CreateLogGroup",
          "logs:CreateLogStream",
        ],
        resources: [logGroup.logGroupArn],
      })
    );

    // Firehose Delivery Stream
    this.deliveryStream = new CfnDeliveryStream(this, "DatasetFirehose", {
      deliveryStreamName: "firehose-to-s3",
      deliveryStreamType: "DirectPut",
      s3DestinationConfiguration: {
        bucketArn: props.destinationBucket.bucketArn,
        roleArn: firehoseRole.roleArn,
        prefix: "dataset/",
        bufferingHints: {
          intervalInSeconds: 60,
          sizeInMBs: 5,
        },
        compressionFormat: "UNCOMPRESSED",
        cloudWatchLoggingOptions: {
          enabled: true,
          logGroupName: logGroup.logGroupName,
          logStreamName: logStream.logStreamName,
        },
      },
    });
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;code&gt;lib/retail-ai-insights-stack.ts&lt;/code&gt; and wire it up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/**
* Firehose Stream
**/
new FirehoseToS3(this, "FirehoseToS3", {
  destinationBucket: bronzeBucket,
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deploy and verify the output, this time go to the Kinesis Firehose console&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cdk deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnmum7iqthmafymz1i7zk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnmum7iqthmafymz1i7zk.png" alt="Kinesis Firehose Streams" width="800" height="125"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This completes our infrastructure deployment for our first phase. data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6 - Simulating Real-Time Events Using Python Scripts
&lt;/h3&gt;

&lt;p&gt;Now that the infrastructure is in place, let’s simulate user activity by streaming mock sales data into our Firehose delivery stream and storing essential product metadata in DynamoDB.&lt;/p&gt;

&lt;h4&gt;
  
  
  What We’re Doing
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Sending user interaction events (like purchases, product views) into the Bronze zone via Firehose.&lt;/li&gt;
&lt;li&gt;Writing product-level information (with inventory and forecasted demand fields) directly into DynamoDB.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This gives us two distinct but complementary data flows: one for historical processing (S3), and one for operational lookups (DynamoDB).&lt;/p&gt;

&lt;h4&gt;
  
  
  Running the Simulation Scripts
&lt;/h4&gt;

&lt;p&gt;Clone the scripts directory directly into your project root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://github.com/mohsinsheikhani/retail-ai-insights/tree/main/scripts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  1. Stream to Firehose
&lt;/h4&gt;

&lt;p&gt;This script sends historical user events to Firehose in batches (simulating real-time behavior):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python3 ./scripts/stream_to_firehose.py --stream-name firehose-to-s3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once it’s running:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open the Kinesis Firehose Console&lt;/li&gt;
&lt;li&gt;Select the &lt;code&gt;firehose-to-s3&lt;/code&gt; stream&lt;/li&gt;
&lt;li&gt;Scroll down to the monitoring tab&lt;/li&gt;
&lt;li&gt;You’ll start to see metrics update (incoming bytes, delivery success, etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvtov7je2q5ttlrkpalef.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvtov7je2q5ttlrkpalef.png" alt="Firehose Stream Metrics" width="800" height="328"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then check your Bronze S3 bucket via the S3 Console:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Navigate into the bucket named &lt;code&gt;retail-ai-bronze-data&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;You should see a new folder under dataset/ containing your streamed JSON records.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frz0q15u23sjcmtt3rocm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frz0q15u23sjcmtt3rocm.png" alt="Bronze zone S3 Bucket for raw data" width="800" height="207"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Populate DynamoDB
&lt;/h4&gt;

&lt;p&gt;This script writes a subset of product info to DynamoDB:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python3 ./scripts/write_to_dynamodb.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It sends fields like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;product_id, product_name, category, price, rating
And auto-generates:&lt;/li&gt;
&lt;li&gt;current_stock (random between 30–70)&lt;/li&gt;
&lt;li&gt;forecasted_demand (initially set to 0)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once the script runs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open DynamoDB Console&lt;/li&gt;
&lt;li&gt;Click Explore Items&lt;/li&gt;
&lt;li&gt;You’ll see all your ingested product records appear in the &lt;code&gt;RetailInventoryTable&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuib4ztdt5z8ssbh174e7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuib4ztdt5z8ssbh174e7.png" alt="List of items within a DynamoDB Table" width="800" height="485"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This completes our &lt;strong&gt;Phase 1: Ingestion Layer setup&lt;/strong&gt;, where we now have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Realistic product interaction data flowing into S3&lt;/li&gt;
&lt;li&gt;Fast-access inventory data in DynamoDB&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the next phase, we’ll build our ETL pipeline using AWS Glue to clean and enrich this data. Stay tuned!&lt;/p&gt;

&lt;h2&gt;
  
  
  Complete Code for the First Phase
&lt;/h2&gt;

&lt;p&gt;To view the full code for the first phase, &lt;a href="https://github.com/mohsinsheikhani/retail-ai-insights/tree/main/01-data-ingestion" rel="noopener noreferrer"&gt;checkout the repository on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🚀 &lt;strong&gt;Follow me on &lt;a href="https://www.linkedin.com/in/mohsin-sheikhani/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; for more AWS content!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>mlops</category>
      <category>dataengineering</category>
    </item>
  </channel>
</rss>
