<?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: Mohamed Sorour</title>
    <description>The latest articles on DEV Community by Mohamed Sorour (@sorour).</description>
    <link>https://dev.to/sorour</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2966313%2F3c7b0a8f-b43b-421a-8203-3bfcfc420899.jpeg</url>
      <title>DEV Community: Mohamed Sorour</title>
      <link>https://dev.to/sorour</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sorour"/>
    <language>en</language>
    <item>
      <title>Chess.com for Debugging: How BugHunt Runs Real-Time Multiplayer on DynamoDB with Zero Servers</title>
      <dc:creator>Mohamed Sorour</dc:creator>
      <pubDate>Sun, 28 Jun 2026 20:43:09 +0000</pubDate>
      <link>https://dev.to/sorour/chesscom-for-debugging-how-bughunt-runs-real-time-multiplayer-on-dynamodb-with-zero-servers-26dh</link>
      <guid>https://dev.to/sorour/chesscom-for-debugging-how-bughunt-runs-real-time-multiplayer-on-dynamodb-with-zero-servers-26dh</guid>
      <description>&lt;p&gt;Every developer debugs. It's the skill you use more than any algorithm — reading unfamiliar code, spotting what's wrong, fixing it under pressure. Yet there's no competitive platform for it. LeetCode tests algorithms. HackerRank tests data structures. Nothing tests the skill you actually use at 2am in production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;BugHunt changes that.&lt;/strong&gt; It's a real-time 1v1 debugging game built to handle a million daily active users on a single DynamoDB table with no servers to manage: two players are matched by Elo rating, see the same buggy code snippet, race to identify the bug, and earn chess-style ratings. Three rounds, 120 seconds each, fastest accurate answer wins. At 1M DAU × 5 games/day that's ~290 game resolves per second at peak — all absorbed by DynamoDB on-demand, with no provisioned capacity, no hot-partition tricks, and no compute layer beyond Vercel serverless functions. It's live at &lt;a href="https://bughunt-beryl.vercel.app" rel="noopener noreferrer"&gt;bughunt-beryl.vercel.app&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fscodwpmihzcby3spuikx.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fscodwpmihzcby3spuikx.png" alt=" " width="799" height="388"&gt;&lt;/a&gt;&lt;br&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqnckm4da1z6lrjvj4n7o.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqnckm4da1z6lrjvj4n7o.png" alt=" " width="800" height="391"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Why DynamoDB — and Why It Scales
&lt;/h2&gt;

&lt;p&gt;The H0 hackathon Track 3 asks for a million-scale global app. Before writing a line of code, I did the math: 1M DAU × 5 games/day = 5M games/day ≈ &lt;strong&gt;58 games/s average, ~290/s at peak&lt;/strong&gt; (5× burst). Each game generates ~12 DynamoDB writes across game state, player answers, and Elo updates — call it 3,500 WCU/s peak. DynamoDB on-demand handles this trivially: UUID partition keys distribute writes uniformly, there are no hot partitions, and capacity scales automatically with no provisioning.&lt;/p&gt;

&lt;p&gt;The table uses Global Tables replicated to us-east-1, eu-west-1, and ap-southeast-1 — reads route to the nearest region. The leaderboard is a materialized view (constant-time reads at any user count, explained below). Every race condition is settled by ConditionExpressions (never locks). The result: a multiplayer game that scales horizontally without any changes to the application layer.&lt;/p&gt;

&lt;p&gt;When I listed every read and write the app would make, every single one was either a key lookup or a single-partition query:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What&lt;/th&gt;
&lt;th&gt;Key pattern&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Load a profile&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;USER#id&lt;/code&gt; / &lt;code&gt;PROFILE&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;1 RCU&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Find active game&lt;/td&gt;
&lt;td&gt;GSI1: &lt;code&gt;ACTIVE_GAME#userId&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;1 query&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Load game + my answers&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;GAME#id&lt;/code&gt; / &lt;code&gt;META&lt;/code&gt;, &lt;code&gt;PLAYER#uid&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;2 reads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Submit an answer&lt;/td&gt;
&lt;td&gt;Conditional update on &lt;code&gt;answers[i]&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;1 WCU&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Top-100 leaderboard&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;LEADERBOARD#GLOBAL&lt;/code&gt; descending, limit 100&lt;/td&gt;
&lt;td&gt;1 query&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;No joins. No aggregations at read time. This is DynamoDB's sweet spot — and on-demand capacity means I never provision anything. The table scales with traffic, period.&lt;/p&gt;
&lt;h2&gt;
  
  
  One Table, Every Entity
&lt;/h2&gt;

&lt;p&gt;Everything in BugHunt lives in one DynamoDB table called &lt;code&gt;bughunt-main&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Users&lt;/strong&gt; — profiles, Elo, match history, social follows&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Games&lt;/strong&gt; — game state, per-player answers, post-game chat&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bugs&lt;/strong&gt; — the actual code snippets with options and explanations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Leaderboards&lt;/strong&gt; — materialized RANK# rows, never computed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tournaments&lt;/strong&gt; — brackets and registrations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Organizations&lt;/strong&gt; — team leaderboards&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Daily challenges&lt;/strong&gt; — one per day with streak tracking&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two GSIs cover the non-key access patterns: GSI1 maps active games to users (so matchmaking can find your in-progress game), and GSI2 maps emails to user IDs (for OAuth account linking).&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fakl1ygviaeth4imq6wz6.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fakl1ygviaeth4imq6wz6.png" alt=" " width="800" height="489"&gt;&lt;/a&gt;&lt;br&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fovtir4assw8cik3w2inq.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fovtir4assw8cik3w2inq.png" alt=" " width="800" height="452"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The Hard Part: Multiplayer on Serverless
&lt;/h2&gt;

&lt;p&gt;A real-time multiplayer game is a pile of race conditions. Both players submitting in the same millisecond. Game resolution triggered twice. A rematch accepted from both ends simultaneously. A tournament slot claimed by two people.&lt;/p&gt;

&lt;p&gt;In a traditional backend, you'd use database transactions or mutex locks. On serverless (no persistent state, no shared memory), those don't exist. Instead, I settled every race condition with &lt;strong&gt;DynamoDB ConditionExpressions&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Submit answer — only succeeds if this round hasn't been answered yet
ConditionExpression: "attribute_not_exists(answers[2].submittedAt)"

// Resolve game — only one request wins
ConditionExpression: "#status = :active"

// Join private game — exactly-once player 2 assignment
ConditionExpression: "attribute_not_exists(player2Id) OR player2Id = :null"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pattern is consistent: every write that can race carries a condition. The first writer wins; the second gets a &lt;code&gt;ConditionalCheckFailedException&lt;/code&gt; and handles it gracefully (usually by returning the current state). No corruption, no locks, no transactions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Leaderboard Is a Materialized View
&lt;/h2&gt;

&lt;p&gt;Most leaderboards scan every user, sort by score, and return the top N. That's an O(n) query that gets slower as your user base grows.&lt;/p&gt;

&lt;p&gt;BugHunt's leaderboard is &lt;strong&gt;written, never read-computed&lt;/strong&gt;. Here's how:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;When a game resolves, &lt;code&gt;resolveGame()&lt;/code&gt; updates both players' Elo on their profiles, then stamps &lt;code&gt;p1EloBefore&lt;/code&gt;, &lt;code&gt;p1EloAfter&lt;/code&gt;, &lt;code&gt;p2EloBefore&lt;/code&gt;, &lt;code&gt;p2EloAfter&lt;/code&gt; onto the game META item.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;That MODIFY event flows through &lt;strong&gt;DynamoDB Streams&lt;/strong&gt; to a Lambda function.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The Lambda moves each player's &lt;code&gt;RANK#&amp;lt;zero-padded-elo&amp;gt;#&amp;lt;userId&amp;gt;&lt;/code&gt; row under the &lt;code&gt;LEADERBOARD#GLOBAL&lt;/code&gt; partition. Delete the old Elo row, write the new one.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Reading the top 100 is one descending Query on the leaderboard partition. Constant time, regardless of user count.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The zero-padding trick (&lt;code&gt;001262&lt;/code&gt; for Elo 1262) turns DynamoDB's lexicographic sort key ordering into a numeric ranking. A per-user cursor row keyed by monotonic &lt;code&gt;gamesPlayed&lt;/code&gt; count guards against cross-shard event reordering — even if two games for the same player arrive out of order, the cursor ensures the latest Elo always wins.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real Multiplayer: Elo-Bucketed Matchmaking
&lt;/h2&gt;

&lt;p&gt;When you click Find Match, BugHunt pushes your Elo rating into a Redis sorted set bucketed by skill range. The next player in a compatible Elo band atomically claims the match — a &lt;code&gt;ZREM&lt;/code&gt; that returns &lt;code&gt;&amp;gt; 0&lt;/code&gt; is the only valid claim; two concurrent callers can't both grab the same opponent. The winner creates a game in DynamoDB with a conditional write, and both clients get their game ID back within milliseconds. If the &lt;code&gt;ZREM&lt;/code&gt; wins but the game creation later fails (no bugs available, conditional conflict), the claimed opponent is re-enqueued — no one gets silently dropped.&lt;/p&gt;

&lt;p&gt;This is the primary experience: humans racing humans at similar skill levels. Bots only enter when you've waited more than ten seconds with no human in range.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bots Without Servers
&lt;/h2&gt;

&lt;p&gt;Hackathon demos die on empty matchmaking queues. But running a bot server defeats the "zero servers" architecture. BugHunt's bots have &lt;strong&gt;no process at all&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When a player has been in the matchmaking queue for 10 seconds with no human opponent, the next matchmake poll creates a game against a bot. From that point, the human's own requests power the bot's turns:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Every status poll / SSE tick calls &lt;code&gt;maybePlayBotRound(game)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;It checks whether the bot's deterministic think delay (seeded from &lt;code&gt;sha256(gameId:round)&lt;/code&gt;) has elapsed&lt;/li&gt;
&lt;li&gt;If yes, it writes the bot's answer through the exact same conditional-write path humans use&lt;/li&gt;
&lt;li&gt;If another concurrent request already wrote it, the conditional write fails silently&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The bot's answer quality is Elo-calibrated: a probability curve based on the bot's rating vs. the bug's difficulty, optionally overridden by Amazon Bedrock Nova (which literally reads the code and picks an answer with a 2.5-second timeout).&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-Time Without WebSockets
&lt;/h2&gt;

&lt;p&gt;Vercel serverless functions can't hold WebSocket connections. BugHunt uses &lt;strong&gt;Server-Sent Events (SSE)&lt;/strong&gt; with layered fallbacks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Primary&lt;/strong&gt;: TCP-based Redis pub/sub via ioredis — the game route subscribes to a &lt;code&gt;game:&amp;lt;gameId&amp;gt;&lt;/code&gt; channel and pushes events instantly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Safety net&lt;/strong&gt;: 10-second DynamoDB poll inside the same SSE connection catches anything pub/sub misses&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fallback&lt;/strong&gt;: If Redis is completely unavailable, the client polls &lt;code&gt;/api/game/status&lt;/code&gt; every 3 seconds&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This means the game works even if Redis is down — it just updates slightly slower. No single point of failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scale Math — The Honest Version
&lt;/h2&gt;

&lt;p&gt;Rather than hand-wave "it'll scale," I documented every known limit and its mitigation path:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Flow&lt;/th&gt;
&lt;th&gt;Peak load&lt;/th&gt;
&lt;th&gt;Verdict&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Game writes (~12 writes/game)&lt;/td&gt;
&lt;td&gt;~3,500 WCU/s&lt;/td&gt;
&lt;td&gt;On-demand + UUID keys absorb trivially&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Matchmaking (zadd/zrange/zrem)&lt;/td&gt;
&lt;td&gt;~700 ops/s @ 2K concurrent&lt;/td&gt;
&lt;td&gt;Redis sorted sets, sharded by Elo band (~15 buckets)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Leaderboard reads&lt;/td&gt;
&lt;td&gt;~1 Query/60s/warm instance&lt;/td&gt;
&lt;td&gt;In-memory cache; effectively free&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSE (DynamoDB polling fallback)&lt;/td&gt;
&lt;td&gt;~50K reads/s @ 100K concurrent&lt;/td&gt;
&lt;td&gt;DynamoDB fine; real cost is function-hours&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Known limits with mitigation paths:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Leaderboard partition write rate&lt;/strong&gt; — at ~1,000 WCU/partition/s the limit is ~125 resolves/s (≈0.4M DAU). Fix: Lambda skips writes for players whose Elo is far below the top-100 cutoff. &amp;gt;99% of games involve no top-100 candidate at million scale.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;BUG#INDEX item size&lt;/strong&gt; — one item, 400KB cap, safe to ~10K bugs. Fix: shard by difficulty (5 smaller items, same optimistic-versioning write path).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SSE on serverless&lt;/strong&gt; — held-open functions are the dollar cost. Redis TCP pub/sub (when &lt;code&gt;REDIS_URL&lt;/code&gt; is set) reduces per-game DynamoDB reads from 0.5/s to ~0.1/s. Final fallback: client polling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-region writes&lt;/strong&gt; — currently pinned to us-east-1. Fix: multi-region Vercel functions + region-affinity matching (players matched through one queue, game writes share a region).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Full capacity documentation: &lt;a href="https://github.com/mohamedsorour1998/bughunt/blob/main/docs/ARCHITECTURE.md" rel="noopener noreferrer"&gt;docs/ARCHITECTURE.md&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Stack
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Technology&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Frontend&lt;/td&gt;
&lt;td&gt;Next.js 16 App Router (Turbopack)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deployment&lt;/td&gt;
&lt;td&gt;Vercel (global edge)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;DynamoDB single-table, on-demand, Global Tables&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stream processing&lt;/td&gt;
&lt;td&gt;DynamoDB Streams + Lambda&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Real-time coordination&lt;/td&gt;
&lt;td&gt;Upstash Redis (matchmaking, pub/sub, rate limiting)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI&lt;/td&gt;
&lt;td&gt;Amazon Bedrock Nova Lite (bug quality filter, bot opponent)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth&lt;/td&gt;
&lt;td&gt;NextAuth v5 (Google + GitHub OAuth)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scheduling&lt;/td&gt;
&lt;td&gt;Upstash QStash (daily challenge, tournament tick)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Single-table design is a forcing function.&lt;/strong&gt; You must know every access pattern before you write a line of code. That sounds painful, but it means your data model serves your app perfectly — no ORM surprises, no N+1 queries, no migration headaches.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On-demand DynamoDB makes 95% of scale free.&lt;/strong&gt; The engineering is in the other 5%: hot partitions, item-size ceilings, and held-open serverless functions. Do that math in the open. Your future self — and apparently hackathon judges — will thank you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ConditionExpressions replace locks.&lt;/strong&gt; Every race condition in a multiplayer game can be expressed as a conditional write. The mental model shift from "acquire lock, do work, release lock" to "attempt write, handle rejection" is genuinely better for serverless architectures.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Try BugHunt:&lt;/strong&gt; &lt;a href="https://bughunt-beryl.vercel.app" rel="noopener noreferrer"&gt;bughunt-beryl.vercel.app&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Source code:&lt;/strong&gt; &lt;a href="https://github.com/mohamedsorour1998/bughunt" rel="noopener noreferrer"&gt;github.com/mohamedsorour1998/bughunt&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Built for the H0 Hackathon — Vercel + AWS Databases. #H0Hackathon&lt;/em&gt;&lt;/p&gt;

</description>
      <category>h0hackathon</category>
    </item>
    <item>
      <title>How an AWS Agent Helped Me Optimize Resources Effortlessly</title>
      <dc:creator>Mohamed Sorour</dc:creator>
      <pubDate>Tue, 30 Sep 2025 05:18:21 +0000</pubDate>
      <link>https://dev.to/aws-builders/how-an-aws-agent-helped-me-optimize-resources-effortlessly-59hi</link>
      <guid>https://dev.to/aws-builders/how-an-aws-agent-helped-me-optimize-resources-effortlessly-59hi</guid>
      <description>&lt;h1&gt;
  
  
  AWS Resource Optimizer Agent
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://github.com/mohamedsorour1998/AWS-Resource-Optimizer-Agent" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I built a production-ready &lt;strong&gt;AWS Resource Optimizer Agent&lt;/strong&gt; with &lt;strong&gt;Amazon Bedrock AgentCore Gateway&lt;/strong&gt;, &lt;strong&gt;Claude Sonnet 4.5&lt;/strong&gt;, and the &lt;strong&gt;Strands Agents SDK&lt;/strong&gt;, and it completely transformed how I manage my cloud environment. Instead of digging through CloudWatch dashboards or juggling CLI commands, I can now just ask questions like &lt;em&gt;“Which EC2 instances are underutilized?”&lt;/em&gt; or &lt;em&gt;“Show me Lambda errors from the last 24 hours”&lt;/em&gt;—and the agent instantly pulls the right metrics, logs, or EBS data from over &lt;strong&gt;137 AWS tools&lt;/strong&gt;. I can run it locally for testing or deploy it to &lt;strong&gt;AgentCore Runtime&lt;/strong&gt; for secure, scalable use, with built-in &lt;strong&gt;OAuth, memory, and semantic search&lt;/strong&gt; that make it simple and powerful. Setup took only a few steps—create an IAM role, load the Smithy specs for CloudWatch, Logs, and EBS, and the agent was live. It saves me hours of manual work and gives me confidence my infrastructure is running efficiently—I can’t imagine going back.&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%2Frc880c782kpiwgn6wpdu.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%2Frc880c782kpiwgn6wpdu.png" alt=" " width="800" height="894"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  📋 Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Overview&lt;/li&gt;
&lt;li&gt;Architecture&lt;/li&gt;
&lt;li&gt;
Deployment Options

&lt;ul&gt;
&lt;li&gt;Option 1: Local Development&lt;/li&gt;
&lt;li&gt;Option 2: AgentCore Runtime&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;API Targets &amp;amp; Capabilities&lt;/li&gt;

&lt;li&gt;Setup Guide&lt;/li&gt;

&lt;li&gt;Configuration Reference&lt;/li&gt;

&lt;li&gt;Troubleshooting&lt;/li&gt;

&lt;li&gt;Project Structure&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What It Does
&lt;/h3&gt;

&lt;p&gt;AI-powered agent that monitors and optimizes AWS resources through natural language conversations using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CloudWatch Metrics API&lt;/strong&gt;: Monitor EC2, RDS, Lambda, S3, EBS performance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CloudWatch Logs API&lt;/strong&gt;: Analyze application and infrastructure logs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EBS API&lt;/strong&gt;: Manage volumes and snapshots&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Key Features
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;137 AWS Tools Access&lt;/strong&gt;:  in AgentCore GatewayCloudWatch, Logs, and EBS APIs via AgentCore Gateway
&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%2F6ews3vw0stnzvodpkhlw.png" alt=" " width="800" height="355"&gt;
&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Semantic Search&lt;/strong&gt;: Intelligent tool discovery via AgentCore Gateway
&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%2Ffb7yc3uy6y46201mbder.png" alt=" " width="800" height="336"&gt;
&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Persistent Memory&lt;/strong&gt;: Cross-session conversation continuity
&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%2F6lrhg7y3h18dyt7h9zcj.png" alt=" " width="800" height="356"&gt;
&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Ready&lt;/strong&gt;: Ready local CLI and in progress to be Deployable to AgentCore Runtime
&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%2Fo1bghpfjwuec9478eo4g.png" alt=" " width="800" height="356"&gt;
&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;OAuth Secured&lt;/strong&gt;: Cognito-based authentication
&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%2Fpvgxy5sta61pt75pmaqi.png" alt=" " width="800" height="354"&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Use Cases
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Identify idle or underutilized resources&lt;/li&gt;
&lt;li&gt;Analyze Lambda function errors&lt;/li&gt;
&lt;li&gt;Monitor RDS database performance&lt;/li&gt;
&lt;li&gt;Search CloudWatch logs for issues&lt;/li&gt;
&lt;li&gt;Optimize EBS storage usage&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%2F66p6l2uaoa0ndefxajqz.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%2F66p6l2uaoa0ndefxajqz.png" alt=" " width="800" height="511"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture
&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%2F8xmvuxrdbjhv5w07mtzn.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%2F8xmvuxrdbjhv5w07mtzn.png" alt=" " width="720" height="402"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Components
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;AgentCore Gateway&lt;/strong&gt;: MCP protocol server exposing AWS APIs as tools&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude Sonnet 4.5&lt;/strong&gt;: LLM for natural language understanding&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strands Agents&lt;/strong&gt;: Agent orchestration framework&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AgentCore Memory&lt;/strong&gt;: Persistent conversation storage&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cognito&lt;/strong&gt;: OAuth 2.0 authentication&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Deployment Options
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Option 1: Local Development (CLI)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Best for&lt;/strong&gt;: Development, testing, rapid iteration&lt;/p&gt;

&lt;h4&gt;
  
  
  Prerequisites
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Python 3.10+&lt;/li&gt;
&lt;li&gt;AWS credentials configured&lt;/li&gt;
&lt;li&gt;Smithy API specs in S3 (&lt;code&gt;cost-explorer-smithy-api&lt;/code&gt; bucket)&lt;/li&gt;
&lt;li&gt;IAM permissions for bedrock-agentcore, IAM, Cognito&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Setup Steps
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Install dependencies&lt;/span&gt;
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt

&lt;span class="c"&gt;# 2. Create infrastructure&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;setup
python3 01-create-iam-role.py
python3 02-create-gateway.py
python3 03-create-smithy-targets.py

&lt;span class="c"&gt;# Wait 60 seconds for tools to sync&lt;/span&gt;

&lt;span class="c"&gt;# 3. Run agent locally&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; ..
python3 agent.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Usage&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 agent.py
👤 You: List my CloudWatch metrics
🤖 Agent: &lt;span class="o"&gt;[&lt;/span&gt;Shows available metrics...]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Option 2: AgentCore Runtime (Production)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Best for&lt;/strong&gt;: Production deployment, managed hosting, team environments&lt;/p&gt;

&lt;h4&gt;
  
  
  Prerequisites
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;AWS Account with bedrock-agentcore permissions&lt;/li&gt;
&lt;li&gt;Python 3.10+&lt;/li&gt;
&lt;li&gt;Boto3 installed&lt;/li&gt;
&lt;li&gt;Model access: Anthropic Claude Sonnet 4.5 enabled in Bedrock console&lt;/li&gt;
&lt;li&gt;Infrastructure from Option 1 setup (Gateway, Targets, IAM Role)&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Deployment Steps
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;1. Configure environment variables&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Copy template&lt;/span&gt;
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.template .env

&lt;span class="c"&gt;# Edit .env with your values from config.json&lt;/span&gt;
&lt;span class="nv"&gt;GATEWAY_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://resource-optimizer-gateway-xxx.gateway.bedrock-agentcore.us-east-1.amazonaws.com/mcp
&lt;span class="nv"&gt;COGNITO_CLIENT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your_client_id
&lt;span class="nv"&gt;COGNITO_CLIENT_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your_secret
&lt;span class="nv"&gt;COGNITO_TOKEN_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://your-domain.auth.us-east-1.amazoncognito.com/oauth2/token
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Deploy to AgentCore Runtime&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Option A: Use deploy script&lt;/span&gt;
./deploy.sh

&lt;span class="c"&gt;# Option B: Manual deployment&lt;/span&gt;
pip &lt;span class="nb"&gt;install &lt;/span&gt;bedrock-agentcore-starter-toolkit
agentcore configure &lt;span class="nt"&gt;-e&lt;/span&gt; runtime_agent.py
agentcore launch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Test deployment&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Quick test&lt;/span&gt;
agentcore invoke &lt;span class="s1"&gt;'{"prompt": "List my Lambda functions"}'&lt;/span&gt;

&lt;span class="c"&gt;# Get agent ARN (from output or bedrock_agentcore.yaml)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Invoke from application&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bedrock-agentcore&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="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke_agent_runtime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;agentRuntimeArn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;arn:aws:bedrock-agentcore:us-east-1:ACCOUNT:agent/AGENT_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;payload&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prompt&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;Show me idle EC2 instances&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Process streaming response
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&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;response&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&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;h4&gt;
  
  
  Runtime Management
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;View logs&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check CloudWatch Logs (location shown in deploy output)&lt;/span&gt;
aws logs &lt;span class="nb"&gt;tail&lt;/span&gt; /aws/bedrock-agentcore/agent/YOUR_AGENT_ID &lt;span class="nt"&gt;--follow&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Update agent&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Make code changes, then redeploy&lt;/span&gt;
agentcore launch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Delete runtime&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Via CLI&lt;/span&gt;
aws bedrock-agentcore delete-agent-runtime &lt;span class="nt"&gt;--agent-runtime-arn&lt;/span&gt; YOUR_ARN

&lt;span class="c"&gt;# Or use AWS Console&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  API Targets &amp;amp; Capabilities
&lt;/h2&gt;

&lt;h3&gt;
  
  
  CloudWatch Metrics (40 tools)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;: Monitor AWS resource performance&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Operations&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ListMetrics&lt;/code&gt;: Discover available metrics&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GetMetricStatistics&lt;/code&gt;: Retrieve metric data points&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GetMetricData&lt;/code&gt;: Query multiple metrics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example Queries&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Show me EC2 CPU utilization"&lt;/li&gt;
&lt;li&gt;"List Lambda invocation metrics"&lt;/li&gt;
&lt;li&gt;"Get RDS database read latency"&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  CloudWatch Logs (91 tools)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;: Search and analyze logs&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Operations&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;DescribeLogGroups&lt;/code&gt;: List log groups&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;FilterLogEvents&lt;/code&gt;: Search logs with patterns&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GetLogEvents&lt;/code&gt;: Retrieve log entries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example Queries&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Search Lambda logs for errors"&lt;/li&gt;
&lt;li&gt;"Show recent log events for my application"&lt;/li&gt;
&lt;li&gt;"Find API Gateway 5xx errors"&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  EBS (6 tools)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;: Manage EBS volumes and snapshots&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Operations&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ListSnapshotBlocks&lt;/code&gt;: List snapshot blocks&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GetSnapshotBlock&lt;/code&gt;: Retrieve block data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example Queries&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"List my EBS volumes"&lt;/li&gt;
&lt;li&gt;"Show snapshot details"&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Setup Guide
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Create IAM Role
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Script&lt;/strong&gt;: &lt;code&gt;setup/01-create-iam-role.py&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it creates&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Role name: &lt;code&gt;ResourceOptimizerGatewayRole&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Trust policy: Allows bedrock-agentcore.amazonaws.com&lt;/li&gt;
&lt;li&gt;Permissions: CloudWatch, Logs, EBS, S3 access&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Permissions granted&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"CloudWatch"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"GetMetricStatistics"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ListMetrics"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GetMetricData"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Logs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"DescribeLogGroups"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"FilterLogEvents"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GetLogEvents"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"EBS"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ListSnapshotBlocks"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GetSnapshotBlock"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"S3"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"GetObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ListBucket"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;For&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Smithy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;specs&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Run&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 setup/01-create-iam-role.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Create AgentCore Gateway
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Script&lt;/strong&gt;: &lt;code&gt;setup/02-create-gateway.py&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it creates&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Gateway with MCP protocol&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Semantic Search enabled&lt;/strong&gt; (intelligent tool selection)&lt;/li&gt;
&lt;li&gt;Debug mode for detailed errors&lt;/li&gt;
&lt;li&gt;Cognito OAuth authentication&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;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;protocolConfiguration&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;mcp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;searchType&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;SEMANTIC&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;Critical&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="mi"&gt;137&lt;/span&gt; &lt;span class="n"&gt;tools&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;Run&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 setup/02-create-gateway.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Output&lt;/strong&gt;: Gateway URL stored in &lt;code&gt;config.json&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Create Smithy Targets
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Script&lt;/strong&gt;: &lt;code&gt;setup/03-create-smithy-targets.py&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it creates&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CloudWatchTarget&lt;/strong&gt; (371KB Smithy spec)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CloudWatchLogsTarget&lt;/strong&gt; (645KB Smithy spec)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EBSTarget&lt;/strong&gt; (92KB Smithy spec)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Smithy Specs Required&lt;/strong&gt;:&lt;br&gt;
Upload to S3 bucket &lt;code&gt;cost-explorer-smithy-api&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;cloudwatch-2010-08-01.json&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cloudwatch-logs-2014-03-28.json&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ebs-2019-11-02.json&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Run&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 setup/03-create-smithy-targets.py
&lt;span class="c"&gt;# Wait 60 seconds for tools to sync&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Output&lt;/strong&gt;: Target IDs stored in &lt;code&gt;config.json&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Configuration Reference
&lt;/h2&gt;

&lt;h3&gt;
  
  
  config.json Structure
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"aws"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"account_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"339712964409"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"gateway_role_arn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:iam::ACCOUNT:role/ResourceOptimizerGatewayRole"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cognito"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"user_pool_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"us-east-1_xxx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"client_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"xxx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"client_secret"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"xxx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"discovery_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://cognito-idp.us-east-1.amazonaws.com/..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"token_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://your-domain.auth.us-east-1.amazoncognito.com/oauth2/token"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"gateway"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"resource-optimizer-gateway-xxx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://resource-optimizer-gateway-xxx.gateway.bedrock-agentcore.us-east-1.amazonaws.com/mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"resource-optimizer-gateway"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"smithy_targets"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"XXXXX"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CloudWatchTarget"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"uri"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"s3://cost-explorer-smithy-api/cloudwatch-2010-08-01.json"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"XXXXX"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CloudWatchLogsTarget"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"uri"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"s3://cost-explorer-smithy-api/cloudwatch-logs-2014-03-28.json"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"XXXXX"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EBSTarget"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"uri"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"s3://cost-explorer-smithy-api/ebs-2019-11-02.json"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"memory"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ResourceOptimizerMemory-xxx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ResourceOptimizerMemory"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Environment Variables (AgentCore Runtime)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Required&lt;/span&gt;
&lt;span class="nv"&gt;GATEWAY_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://your-gateway.gateway.bedrock-agentcore.REGION.amazonaws.com/mcp
&lt;span class="nv"&gt;COGNITO_CLIENT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your_client_id
&lt;span class="nv"&gt;COGNITO_CLIENT_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your_client_secret
&lt;span class="nv"&gt;COGNITO_TOKEN_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://your-domain.auth.REGION.amazoncognito.com/oauth2/token

&lt;span class="c"&gt;# Optional&lt;/span&gt;
&lt;span class="nv"&gt;AWS_REGION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;us-east-1  &lt;span class="c"&gt;# Defaults to us-east-1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Local Development Issues
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Agent shows 0 tools
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Cause&lt;/strong&gt;: Targets not synced yet&lt;br&gt;
&lt;strong&gt;Fix&lt;/strong&gt;: Wait 60 seconds after running &lt;code&gt;03-create-smithy-targets.py&lt;/code&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Permission denied errors
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Cause&lt;/strong&gt;: Missing IAM permissions&lt;br&gt;
&lt;strong&gt;Fix&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Add AgentCore permissions to your IAM user&lt;/span&gt;
aws iam put-user-policy &lt;span class="nt"&gt;--user-name&lt;/span&gt; YOUR_USER &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--policy-name&lt;/span&gt; AgentCoreAdmin &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--policy-document&lt;/span&gt; &lt;span class="s1"&gt;'{
    "Version": "2012-10-17",
    "Statement": [{
      "Effect": "Allow",
      "Action": "bedrock-agentcore:*",
      "Resource": "*"
    }]
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Smithy spec too large error
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Cause&lt;/strong&gt;: Spec exceeds 2MB limit&lt;br&gt;
&lt;strong&gt;Fix&lt;/strong&gt;: We use specs under 2MB (CloudWatch 371KB, Logs 645KB, EBS 92KB)&lt;/p&gt;

&lt;h3&gt;
  
  
  AgentCore Runtime Issues
&lt;/h3&gt;

&lt;h4&gt;
  
  
  CodeBuild build error
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Check&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;View CodeBuild logs in AWS Console&lt;/li&gt;
&lt;li&gt;Verify IAM permissions include CodeBuild access&lt;/li&gt;
&lt;li&gt;Check requirements.txt is valid&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Model access denied
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Fix&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Enable Anthropic Claude Sonnet 4.5 in Bedrock console&lt;/li&gt;
&lt;li&gt;Verify region matches (default: us-west-2 for runtime, us-east-1 for gateway)&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Docker not found warning
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Info&lt;/strong&gt;: Can be ignored - CodeBuild doesn't need Docker unless using &lt;code&gt;--local&lt;/code&gt; flag&lt;/p&gt;

&lt;h3&gt;
  
  
  Gateway Issues
&lt;/h3&gt;

&lt;h4&gt;
  
  
  list_gateway_targets returns 0 targets
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: This API has known issues. Use &lt;code&gt;get_gateway_target(targetId=X)&lt;/code&gt; instead&lt;/p&gt;

&lt;h4&gt;
  
  
  Tools not loading after target creation
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Fix&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Wait 60 seconds for synchronization&lt;/li&gt;
&lt;li&gt;Verify S3 bucket contains Smithy specs: &lt;code&gt;aws s3 ls s3://cost-explorer-smithy-api/&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Project Structure
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aws-cost-optimization-agent/
├── agent.py                    # Local CLI agent
├── runtime_agent.py            # AgentCore Runtime agent
├── requirements.txt            # Python dependencies
├── config.json                 # Auto-generated configuration
├── .env.template               # Environment template
├── deploy.sh                   # Runtime deployment script
├── README.md                   # This file
├── COMPLETION-SUMMARY.md       # Project history
└── setup/
    ├── 01-create-iam-role.py       # IAM role creation
    ├── 02-create-gateway.py        # Gateway with semantic search
    └── 03-create-smithy-targets.py # All 3 API targets
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Key Files
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;agent.py&lt;/strong&gt;: Interactive CLI agent for local development&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;runtime_agent.py&lt;/strong&gt;: Deployable agent for AgentCore Runtime&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;config.json&lt;/strong&gt;: Auto-generated by setup scripts (not in git)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;.env&lt;/strong&gt;: Environment variables for runtime (not in git)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Technical Specifications
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Performance
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Startup&lt;/strong&gt;: ~3 seconds (local), ~1 second (runtime)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tool Loading&lt;/strong&gt;: 137 tools (40 CloudWatch + 91 Logs + 6 EBS)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Response Time&lt;/strong&gt;: 2-4 seconds for typical queries&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory&lt;/strong&gt;: Last 5 conversation turns (local), stateless (runtime)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Limits
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Smithy Spec Size&lt;/strong&gt;: 2MB maximum per target&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tool Name Length&lt;/strong&gt;: 64 characters (Bedrock limitation)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context Window&lt;/strong&gt;: 200K tokens (Claude Sonnet 4.5)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate Limits&lt;/strong&gt;: CloudWatch 400+ req/sec (high)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Technologies
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Technology&lt;/th&gt;
&lt;th&gt;Version&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;LLM&lt;/td&gt;
&lt;td&gt;Claude Sonnet 4.5&lt;/td&gt;
&lt;td&gt;20250929&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent Framework&lt;/td&gt;
&lt;td&gt;Strands Agents&lt;/td&gt;
&lt;td&gt;Latest&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gateway&lt;/td&gt;
&lt;td&gt;AgentCore Gateway&lt;/td&gt;
&lt;td&gt;MCP Protocol&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory&lt;/td&gt;
&lt;td&gt;AgentCore Memory&lt;/td&gt;
&lt;td&gt;Short-term (30 days)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth&lt;/td&gt;
&lt;td&gt;Amazon Cognito&lt;/td&gt;
&lt;td&gt;OAuth 2.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runtime&lt;/td&gt;
&lt;td&gt;AgentCore Runtime&lt;/td&gt;
&lt;td&gt;Container-based&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Security
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Authentication Flow
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Agent requests OAuth token from Cognito&lt;/li&gt;
&lt;li&gt;Token passed in Authorization header to Gateway&lt;/li&gt;
&lt;li&gt;Gateway assumes IAM role for AWS API calls&lt;/li&gt;
&lt;li&gt;All communication over TLS&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Best Practices
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;✅ Use least-privilege IAM policies&lt;/li&gt;
&lt;li&gt;✅ Rotate Cognito client secrets regularly&lt;/li&gt;
&lt;li&gt;✅ Enable CloudWatch logging for audit trail&lt;/li&gt;
&lt;li&gt;✅ Use VPC endpoints for private access (optional)&lt;/li&gt;
&lt;li&gt;✅ Review IAM role permissions quarterly&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/" rel="noopener noreferrer"&gt;AgentCore Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/userguide/runtime.html" rel="noopener noreferrer"&gt;AgentCore Runtime Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://strandsagents.com/" rel="noopener noreferrer"&gt;Strands Agents SDK&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://modelcontextprotocol.org/" rel="noopener noreferrer"&gt;Model Context Protocol&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/cloudwatch/" rel="noopener noreferrer"&gt;AWS CloudWatch&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Support
&lt;/h2&gt;

&lt;p&gt;For issues:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check troubleshooting section above&lt;/li&gt;
&lt;li&gt;Review CloudWatch Logs&lt;/li&gt;
&lt;li&gt;Verify configuration in &lt;code&gt;config.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Check AWS service quotas&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;strong&gt;Status&lt;/strong&gt;: ✅ Production Not Ready yet (Still facing some issues in runtime, once i solve it i will update the code and the post)&lt;br&gt;
&lt;strong&gt;Last Updated&lt;/strong&gt;: September 30, 2025&lt;br&gt;
&lt;strong&gt;Total Tools&lt;/strong&gt;: 137 across 3 API targets&lt;/p&gt;

</description>
      <category>python</category>
      <category>ai</category>
      <category>aws</category>
    </item>
    <item>
      <title>Building Smart Learning Assistants with Amazon Nova: Why Hinting Is Better Than Answering</title>
      <dc:creator>Mohamed Sorour</dc:creator>
      <pubDate>Fri, 16 May 2025 04:51:14 +0000</pubDate>
      <link>https://dev.to/sorour/building-smart-learning-assistants-with-amazon-nova-why-hinting-is-better-than-answering-59pg</link>
      <guid>https://dev.to/sorour/building-smart-learning-assistants-with-amazon-nova-why-hinting-is-better-than-answering-59pg</guid>
      <description>&lt;p&gt;Have you ever had a teacher who simply provided you with the solutions to your homework? Yes,that was satisfying at the time, but did you really gain any knowledge? Most likely not.&lt;/p&gt;

&lt;p&gt;That was the main obstacle we had to overcome in order to create an AI helper for RosettaCloud's interactive Labs. Our platform teaches shell programming and DevOps, and we wanted to develop an AI assistant that would do more than just solve problems for students—it should help them learn.&lt;/p&gt;

&lt;p&gt;In this post, I'll describe how we used LangChain, Amazon Bedrock's Nova LLM, and RAG (Retrieval-Augmented Generation) system to create a hint-first instructional chatbot that have a knowledge base and a memory, Instead of depriving pupils of educational chances, our method guarantees that the assistant leads them toward discoveries.&lt;/p&gt;

&lt;p&gt;The "Don't Just Tell Me" Philosophy&lt;br&gt;
When learning technical skills like DevOps, Shell Scripting or Kubernetes, students need to develop troubleshooting abilities and pattern recognition. If an AI simply answers questions directly, it short-circuits the learning process.&lt;/p&gt;

&lt;p&gt;Here is how our interactive-lab will look like:&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%2F9dvpfn551g0sjxbx41rq.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%2F9dvpfn551g0sjxbx41rq.png" alt="Image description" width="800" height="383"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For now lets focus on AI our AI assistant:&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%2Fnez10c13kyo0xqvucoka.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%2Fnez10c13kyo0xqvucoka.png" alt="Image description" width="512" height="1050"&gt;&lt;/a&gt;&lt;br&gt;
Here's our educational philosophy baked into our AI assistant:&lt;/p&gt;

&lt;p&gt;First interaction: Provide a hint that guides the student toward the solution&lt;br&gt;
Follow-up questions: Provide more direct assistance if the student is still stuck&lt;br&gt;
Context-aware memory: Remember the conversation to provide increasingly targeted help&lt;br&gt;
Nova's strength in context understanding and its ability to follow complex instructions made it perfect for this approach. Let's dive into how we built it.&lt;/p&gt;

&lt;p&gt;Architecture Overview: The RosettaCloud Chatbot Flow&lt;/p&gt;

&lt;p&gt;Our system follows a 7-step flow:&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%2Fecbb41klyg1tv9d4wlyn.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%2Fecbb41klyg1tv9d4wlyn.png" alt="Image description" width="329" height="843"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;1- User asks a question in the Angular frontend&lt;br&gt;
2- Question is sent via WebSockets to API Gateway&lt;br&gt;
3- The AI Chatbot Lambda processes the request&lt;br&gt;
4- Two parallel operations:&lt;br&gt;
 A- Fetch chat history from DynamoDB&lt;br&gt;
 B- Search for relevant documents in our LanceDB vector database&lt;br&gt;
5- Amazon Bedrock's Nova processes everything&lt;br&gt;
6- Response is streamed back to the client&lt;br&gt;
7- Frontend renders the response with helpful references&lt;/p&gt;

&lt;p&gt;This architecture gives us real-time interactions with context awareness, making the student experience feel like working with a patient tutor.&lt;/p&gt;

&lt;p&gt;The Core Components&lt;/p&gt;
&lt;h3&gt;
  
  
  Angular Client side app
&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%2Fgk6kvvduu26mi168war3.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%2Fgk6kvvduu26mi168war3.png" alt="Image description" width="800" height="898"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  WebSocket Communication for Real-Time Responses
&lt;/h3&gt;

&lt;p&gt;Students expect real-time help. We use WebSockets to create a streaming experience:&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%2F6ubk7f2fd2r8smz3xdzg.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%2F6ubk7f2fd2r8smz3xdzg.png" alt="Image description" width="800" height="831"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;typescript
// Angular service for WebSocket communication
@Injectable({
  providedIn: 'root'
})
export class ChatbotService {
  private socket: WebSocket;
  private messageSubject = new Subject&amp;lt;ChatMessage&amp;gt;();

  constructor() {
    this.connectWebSocket();
  }

  private connectWebSocket() {
    // Connect to our API Gateway WebSocket endpoint
    this.socket = new WebSocket(environment.chatbotWebsocketUrl);

    this.socket.onmessage = (event) =&amp;gt; {
      const data = JSON.parse(event.data);
      this.messageSubject.next(data);
    };
  }

  sendQuestion(question: string) {
    const payload = {
      prompt: question,
      session_id: this.getSessionId(),
      bedrock_model_id: 'amazon.nova-lite-v1:0',
      model_kwargs: {
        temperature: 0.7, 
        top_p: 0.9,
        max_tokens_to_sample: 512
      }
    };

    this.socket.send(JSON.stringify(payload));
  }

  getMessages(): Observable&amp;lt;ChatMessage&amp;gt; {
    return this.messageSubject.asObservable();
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Lambda Backend: AI Chatbot with RAG
&lt;/h3&gt;

&lt;p&gt;The heart of our system is the AI Chatbot Lambda which handles:&lt;/p&gt;

&lt;p&gt;Processing the user's question&lt;br&gt;
Retrieving relevant context from our knowledge base&lt;br&gt;
Building a conversational RAG workflow&lt;br&gt;
Streaming responses back to the client&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%2F1nh36bvxk4qbrpgpjv7b.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%2F1nh36bvxk4qbrpgpjv7b.png" alt="Image description" width="800" height="543"&gt;&lt;/a&gt;&lt;br&gt;
Here's a simplified version of our ai_chatbot.py Lambda function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def handle_message(event, context):
    connection_id = event.get('requestContext', {}).get('connectionId', '')
    api_endpoint = get_api_endpoint_from_event(event)

    body = json.loads(event.get("body", "{}"))
    prompt = body["prompt"]
    bedrock_model_id = body["bedrock_model_id"]
    model_kwargs = body["model_kwargs"]
    session_id = body["session_id"]

    try:
        # Initialize our streaming wrapper for Bedrock
        streamer = BedrockStreamer(connection_id, session_id, api_endpoint)

        # Create the RAG chain with LangChain
        conversation = streamer.create_rag_chain(
            LANCEDB_S3_URI, 
            KNOWLEDGE_BASE_ID, 
            bedrock_model_id, 
            model_kwargs
        )

        # Stream the response
        for response in streamer.stream_response(conversation, prompt):
            streamer.params["Data"] = response
            streamer.api_client.post_to_connection(**streamer.params)

        return {"statusCode": 200, "body": json.dumps("Success")}

    except Exception as e:
        print(f"Error: {str(e)}")
        # Error handling...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The BedrockStreamer class is where the magic happens:
&lt;/h3&gt;

&lt;p&gt;Our chatbot system's foundation is the BedrockStreamer class, which manages the intricate interaction between human inquiries and AI answers.  It establishes connections to Amazon Bedrock for AI processing and API Gateway for WebSocket communication.  Fundamentally, this class builds the RAG (Retrieval Augmented Generation) pipeline by setting up the conversational components of LangChain, initializing vector retrievers from LanceDB, defining the "hint-first" instructional prompt method, and controlling the streaming response system.  It manages important activities including using heartbeat messages to sustain WebSocket connections, adding instructional metadata to answers, and using DynamoDB to ensure session persistence.  Instead of functioning as a unified teaching aid, the many elements would remain disjointed fragments in the absence of this overarching orchestration layer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
class BedrockStreamer:
    def __init__(self, connectionId, session_id, api_endpoint):
        self.api_client = boto3.client("apigatewaymanagementapi", 
                                       endpoint_url=api_endpoint)
        self.bedrock_client = boto3.client(service_name='bedrock-runtime', 
                                          region_name=BEDROCK_REGION)
        self.session_id = session_id
        self.doc_sources = []
        self.params = {
            "Data": "",
            "ConnectionId": connectionId
        }
        self.heartbeat_active = False

    def set_prompt(self, response_style="balanced"):
        # The key to our hint-first approach is in the system prompt
        system_prompt = (
            "You are an assistant specializing in DevOps. "
            f"Use a {response_style} style - be direct, clear and efficient. "
            "Use the following pieces of retrieved context to answer "
            "But do not include the context in your answer. "
            "You should not answer the question directly, but rather hint the user "
            "to the answer. if the user asked again, then you can answer. "
            "You should not answer any question not related to DevOps or SWE. "
            "If the user asks about something else, say that you are not able to "
            "answer that. If the user asks about DevOps, answer it. "
            "the question about DevOps. If you don't know the answer, "
            "say that you don't know. When showing code examples, "
            "format them properly with markdown syntax."
            "\n\n"
            "{context}"
        )

        # Conversation prompt for chat history context
        qa_prompt = ChatPromptTemplate.from_messages([
            ("system", system_prompt),
            MessagesPlaceholder(variable_name="chat_history"),
            ("human", "{input}"),
        ])

        return qa_prompt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Nova LLM: The Brain That Gets Educational Context
&lt;/h3&gt;

&lt;p&gt;Amazon Bedrock's Nova model is particularly good at understanding the nuance between "give a hint" and "provide the answer." We configure it to provide educational guidance:&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%2F54328y4pw73gt10vv6tj.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%2F54328y4pw73gt10vv6tj.png" alt="Image description" width="800" height="848"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def create_rag_chain(self, lancedb_uri, knowledge_base_id, bedrock_model_id, model_kwargs):
    # Get our educational prompt template
    qa_prompt = self.set_prompt("educational")

    # Initialize vector retriever for our shell script knowledge base
    retriever = self.init_retriever(lancedb_uri, knowledge_base_id)

    # Initialize Bedrock LLM with Nova
    llm = BedrockChat(
        model_id=bedrock_model_id,
        model_kwargs=model_kwargs,
        streaming=True,
        client=self.bedrock_client
    )

    # Create chain components using LangChain
    history_aware_retriever = create_history_aware_retriever(llm, retriever)
    question_answer_chain = create_stuff_documents_chain(llm, qa_prompt)
    rag_chain = create_retrieval_chain(history_aware_retriever, question_answer_chain)

    # Make it conversational with DynamoDB chat history
    conversational_rag_chain = RunnableWithMessageHistory(
        rag_chain,
        lambda session_id: DynamoDBChatMessageHistory(
            table_name=DYNAMO_TABLE,
            session_id=self.session_id
        ),
        input_messages_key="input",
        history_messages_key="chat_history",
    )

    return conversational_rag_chain
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Stream response back via web sockets:
&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%2Fwi5tf8j311pdd7qng52d.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%2Fwi5tf8j311pdd7qng52d.png" alt="Image description" width="589" height="488"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Angular show it to user:
&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%2Futirep4gmnyqznkbxcnz.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%2Futirep4gmnyqznkbxcnz.png" alt="Image description" width="436" height="852"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Knowledge Base Indexing: Document Indexer Lambda
&lt;/h2&gt;

&lt;p&gt;For our assistant to be effective, it needs access to high-quality learning materials. We built a document indexing system that processes shell scripts and extracts educational metadata.&lt;br&gt;
Our chatbot system's knowledge base is created and maintained via the crucial background operation known as Document Indexing Flow. &lt;/p&gt;

&lt;p&gt;The Document Indexer Lambda is automatically activated by an EventBridge trigger when shell scripts are uploaded to S3. The Lambda meticulously examines each script to collect educational metadata such as the question text, difficulty level, correct answers, and verification logic.  The improved script representations are then converted into vector embeddings that capture semantic meaning using this Lambda using Amazon Bedrock's Titan Embeddings model.  The contextual understanding of the chatbot is powered by a searchable knowledge base created by storing these vectors in LanceDB.  Without human involvement, this automated pipeline guarantees that our chatbot always has access to up-to-date, correctly indexed instructional information, enabling the system to grow naturally as more learning resources are added.&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%2Fq8tqond7cjv2bludc4g5.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%2Fq8tqond7cjv2bludc4g5.png" alt="Image description" width="800" height="118"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def process_shell_script(text, filename):
    # Extract educational metadata from shell script comments
    lab_info = {
        'exercise_name': '',
        'difficulty': '',
        'learning_objectives': [],
        'question_number': '',
        'question': '',
        'question_type': '',
        'possible_answers': [],
        'correct_answer': '',
        'question_flag': '',
        'check_flag': ''
    }

    # Parse the shell script to extract question info
    # from comments and code blocks
    # ...

    # Create an enhanced representation that includes
    # educational context
    enhanced_text = f"""
SCRIPT NAME: {filename}

ORIGINAL CONTENT:
{text}

SUMMARY OF COMMENTS:
{chr(10).join(comments)}

MAIN COMMAND BLOCKS:
{chr(10) + chr(10).join([f"Block {i+1}:{chr(10)}{block}" for i, block in enumerate(command_blocks)])}

LAB QUESTION INFORMATION:
{formatted_lab_metadata}
"""

    return enhanced_text, lab_info
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Hint-First Approach in Action&lt;br&gt;
Let's see how this actually works in practice. Here's a sample interaction:&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%2Frwfrzs1aet5tmcqukt4e.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%2Frwfrzs1aet5tmcqukt4e.png" alt="Image description" width="432" height="845"&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%2Fkyxgdevhuyimnsv9f637.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%2Fkyxgdevhuyimnsv9f637.png" alt="Image description" width="440" height="849"&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%2F4st1x8wumkybis1i1frc.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%2F4st1x8wumkybis1i1frc.png" alt="Image description" width="438" height="850"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons Learned from Building with Nova
&lt;/h2&gt;

&lt;p&gt;After implementing this system, we've learned several valuable lessons:&lt;/p&gt;

&lt;p&gt;Nova does a remarkable job of adhering to subtle instructions and maintaining the "hint first" strategy. Streaming is crucial. Compared to waiting for full answers, the WebSocket method feels considerably more interactive.&lt;/p&gt;

&lt;p&gt;We went through numerous iterations to find the ideal balance between being too helpful and being helpful. Careful prompt engineering matters. Context is king: The assistant is genuinely helpful when it combines conversation history and document retrieval.&lt;/p&gt;

&lt;p&gt;Finding a balance between user delight and instructional value was the most challenging aspect. Students would become irritated if the helper was very restricted. They wouldn't learn if they were too helpful. We were able to achieve that balance because of Nova's adaptability.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Significance of This for Education
&lt;/h2&gt;

&lt;p&gt;There are two main types of traditional approaches to educational chatbots:&lt;/p&gt;

&lt;p&gt;Basic Q&amp;amp;A bots that offer straightforward responses without any direction&lt;br&gt;
Systems that are too tight and irritate kids&lt;/p&gt;

&lt;p&gt;With Nova, our hint-first strategy establishes a compromise—a system that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Promotes critical thinking&lt;/li&gt;
&lt;li&gt;Offers scaffolded assistance&lt;/li&gt;
&lt;li&gt;Increases directness gradually in accordance with student needs&lt;/li&gt;
&lt;li&gt;Keeps in mind the context of the conversation to offer tailored assistance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This reflects the real job of effective teachers. They point students in the direction of answers rather than solving their difficulties.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion: AI in Education's Future
&lt;/h2&gt;

&lt;p&gt;Developing educational AI differs greatly from developing a general-purpose helper. A well-designed RAG system and Amazon Nova's adaptability enabled us to develop something that genuinely improves learning rather than hinders it.&lt;/p&gt;

&lt;p&gt;As we keep improving our system, we're investigating:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More advanced methods for knowledge retrieval &lt;/li&gt;
&lt;li&gt;Progress monitoring to automatically modify hint levels&lt;/li&gt;
&lt;li&gt;Integration with practical laboratory settings for instantaneous feedback&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AI in education is not about substituting answer machines for instructors. The goal is to build assistants that, like the best human teachers, know when to guide, when to instruct, and when to hint.&lt;br&gt;
Have you developed AI systems for education? Which strategies have you found work best for striking a balance between learning support and assistance? Leave a comment with your ideas!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>bedrock</category>
      <category>ai</category>
      <category>cb</category>
    </item>
  </channel>
</rss>
