<?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: Håkon Eriksen Drange</title>
    <description>The latest articles on DEV Community by Håkon Eriksen Drange (@haakoned).</description>
    <link>https://dev.to/haakoned</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%2F2938543%2F32537227-18f1-4abe-a2f9-91b2506ccdd1.jpeg</url>
      <title>DEV Community: Håkon Eriksen Drange</title>
      <link>https://dev.to/haakoned</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/haakoned"/>
    <language>en</language>
    <item>
      <title>AWS Summit Stockholm 2026 talk: Using Amazon Bedrock for creative Christmas spirit ideas</title>
      <dc:creator>Håkon Eriksen Drange</dc:creator>
      <pubDate>Fri, 08 May 2026 12:57:38 +0000</pubDate>
      <link>https://dev.to/haakoned/aws-summit-stockholm-2026-talk-using-amazon-bedrock-for-creative-christmas-spirit-ideas-1321</link>
      <guid>https://dev.to/haakoned/aws-summit-stockholm-2026-talk-using-amazon-bedrock-for-creative-christmas-spirit-ideas-1321</guid>
      <description>&lt;p&gt;On May 7th 2026 I got the opportunity to hold a talk at the Developer Community Zone at the AWS Summit in Stockholm, where I shared my learnings developing the creative Christmas Elf project as described in blog post &lt;a href="https://hedrange.com/2026/03/07/generative-ai-for-creativity-lessons-learned-from-building-a-christmas-elf-idea-platform-with-amazon-bedrock/" rel="noopener noreferrer"&gt;https://hedrange.com/2026/03/07/generative-ai-for-creativity-lessons-learned-from-building-a-christmas-elf-idea-platform-with-amazon-bedrock/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The audience was very engaged and raised many good questions after the presentation. The slides are available for download below.&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%2Fhedrange.com%2Fwp-content%2Fuploads%2F2026%2F05%2FDSC01325-1024x683.jpg" 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%2Fhedrange.com%2Fwp-content%2Fuploads%2F2026%2F05%2FDSC01325-1024x683.jpg" title="DSC01325-1024x683" width="800" height="534"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://hedrange.com/wp-content/uploads/2026/05/AWS-Summit-Stockholm-2026-COM302-English.pdf" rel="noopener noreferrer"&gt;AWS-Summit-Stockholm-2026-COM302-English&lt;/a&gt;&lt;a href="https://hedrange.com/wp-content/uploads/2026/05/AWS-Summit-Stockholm-2026-COM302-English.pdf" rel="noopener noreferrer"&gt;Download&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The post &lt;a href="https://hedrange.com/2026/05/08/aws-summit-stockholm-2026-talk-using-amazon-bedrock-for-creative-christmas-spirit-ideas/" rel="noopener noreferrer"&gt;AWS Summit Stockholm 2026 talk: Using Amazon Bedrock for creative Christmas spirit ideas&lt;/a&gt; first appeared on &lt;a href="https://hedrange.com/" rel="noopener noreferrer"&gt;Håkon Eriksen Drange&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>articles</category>
      <category>aws</category>
      <category>summit</category>
    </item>
    <item>
      <title>Bytefest developer conference talk: From Cowboy Code to Production Gold with Kiro</title>
      <dc:creator>Håkon Eriksen Drange</dc:creator>
      <pubDate>Sat, 28 Mar 2026 17:38:52 +0000</pubDate>
      <link>https://dev.to/haakoned/bytefest-developer-conference-talk-from-cowboy-code-to-production-gold-with-kiro-45cj</link>
      <guid>https://dev.to/haakoned/bytefest-developer-conference-talk-from-cowboy-code-to-production-gold-with-kiro-45cj</guid>
      <description>&lt;p&gt;On Thursday March 17th I held yet another talk about Spec-Driven development, this time at Sopra Steria’s Bytefest developer conference. As the capabilities in this field are evolving fasted I showed a practical demonstration on how to go from solo mode to full team visibility by leveraging the Github MCP server to automagically create and update Github issues for spec refinement and task execution. This ensures ongoing work is visible on a Kanban board, for full transparency to everyone involved, from developers to stakeholders.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Abstract&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Vibe coding can be very useful for prototyping, but do you trust it for production? Get an introduction to the AI-assisted software development lifecycle and how you can use Kiro to ship your product using professional craftmanship practices.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Official program directory: &lt;a href="https://bytefest.soprasteria.no/bytefest-med-venner/talks/1118863" rel="noopener noreferrer"&gt;https://bytefest.soprasteria.no/bytefest-med-venner/talks/1118863&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Github repo with code and examples for the demonstrated weather forecast app: &lt;a href="https://github.com/haakond/terraform-aws-weather-forecast" rel="noopener noreferrer"&gt;https://github.com/haakond/terraform-aws-weather-forecast&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%2F9vxe7g76bb0ewhtds56f.jpg" 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%2F9vxe7g76bb0ewhtds56f.jpg" width="800" height="534"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Slides&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://hedrange.com/wp-content/uploads/2026/03/2026-03-19-bytefest-from-cowboy-code-to-production-gold-with-kiro.pdf" rel="noopener noreferrer"&gt;2026-03-19-bytefest-from-cowboy-code-to-production-gold-with-kiro&lt;/a&gt;&lt;a href="https://hedrange.com/wp-content/uploads/2026/03/2026-03-19-bytefest-from-cowboy-code-to-production-gold-with-kiro.pdf" rel="noopener noreferrer"&gt;Download&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The post &lt;a href="https://hedrange.com/2026/03/28/bytefest-developer-conference-talk-from-cowboy-code-to-production-gold-with-kiro/" rel="noopener noreferrer"&gt;Bytefest developer conference talk: From Cowboy Code to Production Gold with Kiro&lt;/a&gt; first appeared on &lt;a href="https://hedrange.com/" rel="noopener noreferrer"&gt;Håkon Eriksen Drange&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>articles</category>
      <category>aws</category>
      <category>kiro</category>
      <category>specdrivendevelopmen</category>
    </item>
    <item>
      <title>Generative AI for creativity: Lessons learned from building a Christmas Elf idea platform with Amazon Bedrock</title>
      <dc:creator>Håkon Eriksen Drange</dc:creator>
      <pubDate>Sat, 07 Mar 2026 18:05:54 +0000</pubDate>
      <link>https://dev.to/haakoned/generative-ai-for-creativity-lessons-learned-from-building-a-christmas-elf-idea-platform-with-3ejo</link>
      <guid>https://dev.to/haakoned/generative-ai-for-creativity-lessons-learned-from-building-a-christmas-elf-idea-platform-with-3ejo</guid>
      <description>&lt;p&gt;Many Generative and Agentic AI blogs these days are about document processing and business workflow automation. This one is about elves. Read on to learn more about how I built this platform and the lessons that I learned.&lt;/p&gt;

&lt;h3&gt;
  
  
  Table of contents
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;The unique value proposition of Naughty Elf Ideas&lt;/li&gt;
&lt;li&gt;Solution architecture of the Naughty Elf Ideas platform&lt;/li&gt;
&lt;li&gt;
Idea generation flow walk-through

&lt;ul&gt;
&lt;li&gt;Idea generation orchestration with AWS Step Functions&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Video generation flow walkthrough&lt;/li&gt;

&lt;li&gt;Choosing the right tool/model for each job&lt;/li&gt;

&lt;li&gt;Looking into the creative idea generation process&lt;/li&gt;

&lt;li&gt;

Lessons learned

&lt;ul&gt;
&lt;li&gt;Lesson 1: Construct prompts according to model provider guidance&lt;/li&gt;
&lt;li&gt;Lesson 2: Prompt engineering alone wasn't enough, RAG changed everything&lt;/li&gt;
&lt;li&gt;Lesson 3: LLMs doesn't follow "don't do X" – Use positive context instead&lt;/li&gt;
&lt;li&gt;Lesson 4: Multilingual translation challenges (Norwegian and Swedish)&lt;/li&gt;
&lt;li&gt;Lesson 5: AWS Step Functions vs. Amazon Bedrock Agents – Why I chose deterministic orchestration&lt;/li&gt;
&lt;li&gt;Comparison of orchestration workflow alternatives&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

Conclusion

&lt;ul&gt;
&lt;li&gt;Key takeaways&lt;/li&gt;
&lt;li&gt;Future enhancements&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;With the increasingly popular tradition with the Elf on the Shelf (Naughty/Mischevious Elf, or “Rampenissen” in Norwegian), as December approaches, millions of parents all over the world face a similar challenge: what creative scenario should the Christmas elf get into tonight, to surprise their children? After a few years of the tradition, you’ve exhausted the classics; elf in flour mess, elf making snow angels in powdered sugar, elf zip-lining across the living room and toilet paper all over. You find yourself doom-scrolling Facebook groups and Pinterest until midnight, desperately searching for something your kids haven’t seen before.&lt;/p&gt;

&lt;p&gt;At Sopra Steria, we help customers optimize processes and achieve business outcomes with technology. But in this case, I wanted to explore a more creative side – what would be possible when you push the boundaries of generative AI for &lt;em&gt;inspiration&lt;/em&gt; rather than &lt;em&gt;automation&lt;/em&gt;. This is my take on an innovation sandbox.&lt;/p&gt;

&lt;p&gt;To solve this creative idea problem (primarily for my wife, but other parents could also probably profit ;-), just before AWS re:Invent 2025 I got the idea to dive deep into Generative and Agentic AI capabilities with Amazon Bedrock. I figure that a really useful tool would not be a static database of recycled ideas, but a complete platform that generates unlimited unique elf scenarios on demand — tailored to kids’ age, rooms in your house, and the mood you’re after (naughty, nice, nostalgia/pop culture). The result is &lt;a href="https://naughtyelfideas.com/" rel="noopener noreferrer"&gt;https://naughtyelfideas.com/&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The unique value proposition of Naughty Elf Ideas
&lt;/h3&gt;

&lt;p&gt;While traditional blog posts usually cover 20-80 recycled ideas, the Naughty Elf Ideas platform provides unlimited creative AI-generated ideas, based on the latest and most innovative Large Language Models (LLMs) from providers such as Anthropic and Amazon.&lt;/p&gt;

&lt;p&gt;Traditional blogs have 0 personalization. This platform generates ideas according to your preferences for age group, room, mood, and it even accepts custom prompts. The site and ideas are tailored for local culture and language currently in English, Spanish, Norwegian and Swedish, with more to come!&lt;/p&gt;

&lt;p&gt;Regular sites uses common stock photos or poorly lit cell phone snapshots. Here we generate unique images and video snippets of each idea.&lt;/p&gt;

&lt;p&gt;Freshness is also on a whole new level, since new ideas are added by the community continuously. Users can vote for and rank the best ideas and share them in social media.&lt;/p&gt;

&lt;h3&gt;
  
  
  Solution architecture of the Naughty Elf Ideas platform
&lt;/h3&gt;

&lt;p&gt;This is a real, live site which naturally receives the majority of it’s traffic in the months of late November and December. After Christmas, there’s no use for it. That’s why I chose pure serverless components from Amazon Web Services, to be able to scale according to demand. The remaining time of the year the baseline running costs are minimal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key components&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fully serverless – no servers or databases to manage, scales automatically&lt;/li&gt;
&lt;li&gt;AWS Step Functions provides visual workflow orchestration with built-in error handling&lt;/li&gt;
&lt;li&gt;Multiple Amazon Bedrock models selected for specific tasks (more on this below)&lt;/li&gt;
&lt;li&gt;Amazon DynamoDB for idea storage with single-table design&lt;/li&gt;
&lt;li&gt;Amazon S3 + Amazon CloudFront for static assets (images, videos) with cache invalidation&lt;/li&gt;
&lt;li&gt;Next.js on AWS Amplify for SEO-optimized frontend with SSR/SSG&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Overview of AWS architecture components&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%2Fmyq2lpxyr6ruqmi6xing.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%2Fmyq2lpxyr6ruqmi6xing.png" width="800" height="724"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Idea generation flow walk-through
&lt;/h3&gt;

&lt;p&gt;This is how the idea generation dialogue looks 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%2Faqal5ojv6wwtqwjtdy83.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%2Faqal5ojv6wwtqwjtdy83.png" width="800" height="660"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Excited users are not patient, so a challenge has been to optimize end-to-end processing to reduce waiting times to a minimum. Websockets dialogue mimics interaction during processing. The current total end-to-end processing time is about 30 seconds from request to complete idea, including translations to multiple languages and unique image generation. Parallel branches for translation and image generation reduces overall time-to-idea.&lt;/p&gt;

&lt;h4&gt;
  
  
  Idea generation orchestration with AWS Step Functions
&lt;/h4&gt;

&lt;p&gt;One of the key architectural decisions was to identify the optimal workflow orchestration. I started out with Bedrock Agents, but quickly discovered it took well over 60-80 seconds every time. The AI reasoning overhead added latency at each decision point (which also incurred cost).&lt;/p&gt;

&lt;p&gt;For a well-defined and repeatable workflow, there’s actually no need for AI Agent reasoning. The fastest and most cost efficient approach turned out to be a good, old State Machine. In addition, visual insight and debugging with log integrations in AWS Step Functions proved invaluable for troubleshooting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recommendation&lt;/strong&gt; : Use Step Functions when the workflow is well-defined and latency matters; consider Bedrock Agents when you need dynamic reasoning and tool selection.&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%2Fbiinxxvqgouugplhkba4.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%2Fbiinxxvqgouugplhkba4.png" width="800" height="958"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;End user clicks Generate idea along with preferred properties&lt;/li&gt;
&lt;li&gt;A POST /generate-idea call is sent to Amazon API Gateway which starts execution of an AWS Step Functions State Machine for Idea Generation&lt;/li&gt;
&lt;li&gt;To avoid bankrupting me in case the site goes really viral, I’ve set a cap on generation of max 100 ideas per day.&lt;/li&gt;
&lt;li&gt;If a custom prompt was provided in another language than English, translate it to English, which is the primary language of the system.&lt;/li&gt;
&lt;li&gt;Lambda function GenerateCreativeIdea invokes an Amazon Bedrock Agent wired up with a Bedrock Knowledge Base, to provide relevant context of historical ideas.

&lt;ul&gt;
&lt;li&gt;I found the most creative ideas were provided by the latest version of Anthropic Sonnet.&lt;/li&gt;
&lt;li&gt;An Amazon Bedrock Guardrail is applied to ensure family friendly and safe ideas. &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;When we have an original idea we enter a ParallelProcessing state.

&lt;ul&gt;
&lt;li&gt;Translations to Norwegian, Spanish and Swedish – two-pass using Anthropic Haiku.&lt;/li&gt;
&lt;li&gt;Image generation with Amazon Nova Canvas based on idea description.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Upon successful results the generated idea is stored in Amazon DynamoDB along with relevant metadata.&lt;/li&gt;
&lt;li&gt;The daily idea generator counter is incremented and relevant website pages are invalidated from Amazon Cloudfront.&lt;/li&gt;
&lt;li&gt;The final step is to put a message on an Amazon Simple Queue System (SQS) queue to generate a video snippet. This takes 2-3 minutes, and I didn’t want to keep the user waiting, so this is the reason for the decoupled, asynchronous process.
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Video generation flow walkthrough
&lt;/h3&gt;

&lt;p&gt;Same approach here; repeatable process =&amp;gt; AWS Step Functions.&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%2Fyncuc1oknjic77mez1dm.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%2Fyncuc1oknjic77mez1dm.png" width="800" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Amazon SQS queue which receives the message from the Idea Generation State Machine is configured with an AWS Lambda trigger which invokes the Video Generation State Machine.&lt;/li&gt;
&lt;li&gt;The AWS Lambda function GenerateVideo invokes Amazon Bedrock for video generation with &lt;a href="https://docs.aws.amazon.com/nova/latest/userguide/video-generation.html" rel="noopener noreferrer"&gt;Amazon Nova Reel.&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;The original image is supplied for relevance.&lt;/li&gt;
&lt;li&gt;Raw video output is stored in Amazon S3.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;AWS Step Function logic waits and polls for video generation success or failure.&lt;/li&gt;
&lt;li&gt;When video generation completes, another Docker based AWS Lambda function uses FFmpeg to transcode the raw video file from S3 for web and file size optimization (for fast loading and data transfer cost control. An optimized version is saved back to Amazon S3 in a path configured as Amazon Cloudfront origin.&lt;/li&gt;
&lt;li&gt;The original idea is updated with metadata that a video is available. &lt;/li&gt;
&lt;li&gt;The main idea web page is invalidated from Amazon Cloudfront and the next page load displays a video overlay instead of the original image.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;End-to-end this takes about 2 minutes and 25 seconds for a 6 second clip (current limitation in Amazon Nova Reel). Videos are progressive enhancements, they do not block the main idea flow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Choosing the right tool/model for each job
&lt;/h3&gt;

&lt;p&gt;To be able to get the results you are looking for it’s imperative to evaluate and benchmark relevant LLMs for different purposes. I ended up with the following:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Task&lt;/th&gt;
&lt;th&gt;Model selected&lt;/th&gt;
&lt;th&gt;Rationale&lt;/th&gt;
&lt;th&gt;Alternatives considered&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Creative idea generation&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.anthropic.com/claude/sonnet" rel="noopener noreferrer"&gt;Anthropic Claude Sonnet 4.6&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Most creative, contextually appropriate ideas&lt;/td&gt;
&lt;td&gt;Claude Haiku (less creative), Nova Pro (good but less nuanced)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Norwegian and Swedish translation&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.anthropic.com/claude/haiku" rel="noopener noreferrer"&gt;Anthropic Claude Haiku 4.5 (two-pass)&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Natural-sounding Norwegian/Swedish via two-pass approach&lt;/td&gt;
&lt;td&gt;Nova models (did not produce acceptable results, suspect low training on these languages), Amazon Translate (too literal)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spanish translation&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.anthropic.com/claude/haiku" rel="noopener noreferrer"&gt;Anthropic Claude Haiku 4.5 (two-pass)&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Consistent quality across languages&lt;/td&gt;
&lt;td&gt;Nova 2 Lite (acceptable, but standardized on Haiku as this was required for NO/SE)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Image generation&lt;/td&gt;
&lt;td&gt;&lt;a href="https://docs.aws.amazon.com/ai/responsible-ai/nova-canvas/overview.html" rel="noopener noreferrer"&gt;Amazon Nova Canvas&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Cost-effective, good quality, AWS-native&lt;/td&gt;
&lt;td&gt;Stability AI (higher quality, higher cost)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Video generation&lt;/td&gt;
&lt;td&gt;&lt;a href="https://docs.aws.amazon.com/ai/responsible-ai/nova-reel/overview.html" rel="noopener noreferrer"&gt;Amazon Nova Reel&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Only AWS-native option, 6-second clips (without complex stitching)&lt;/td&gt;
&lt;td&gt;None (unique capability)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Anthropic Claude Sonnet for ideas&lt;/strong&gt; : Tested multiple models – Sonnet consistently produced the most creative, contextually appropriate elf ideas&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude Haiku for Norwegian&lt;/strong&gt;  &lt;strong&gt;and Swedish&lt;/strong&gt; : This was the hardest problem. Amazon Nova models were not acceptable out of the box for Scandinavian languages, and that’s not a surprise, taking into account the small population compared to the world’s major languages. Training on material from these languages have probably not been prioritized. Amazon Translate was even worse (too literal). The solution: a two-pass approach with Claude Haiku that first translates, then refines for natural flow, using Anthropic’s prompt guidelines. Low temperature (0.1) provides consistent translations. I discovered that higher temperature lead to creative interpretations and invention of new words :-).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Amazon&lt;/strong&gt;  &lt;strong&gt;Nova Canvas for images&lt;/strong&gt; : Good balance of quality and cost. Stability AI was said to produce very high quality, but at 3x the cost, I went with Nova Canvas. I am looking forward to full Nova V2 suite availability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Amazon&lt;/strong&gt;  &lt;strong&gt;Nova Reel for videos&lt;/strong&gt; : The only AWS-native option for video generation. 6-second clips are perfect for social media sharing and preserving bandwidth charges.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key take-away&lt;/strong&gt; : Model selection should be validated through experimentation for each use case — don’t assume based on benchmarks. As new minor versions are released from the providers, expect upgrades in quality and relevance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Looking into the creative idea generation process
&lt;/h3&gt;

&lt;p&gt;Each idea on the site is generated on demand by an &lt;a href="https://docs.aws.amazon.com/bedrock/latest/userguide/agents.html" rel="noopener noreferrer"&gt;Amazon Bedrock Agent&lt;/a&gt; backed by an &lt;a href="https://docs.aws.amazon.com/bedrock/latest/userguide/knowledge-base.html" rel="noopener noreferrer"&gt;Amazon Bedrock Knowledge Base&lt;/a&gt; of existing elf ideas. When a request comes in, the agent doesn’t just freestyle, it receives a structured prompt that includes the target age group, the mood of the idea (naughty, nice, or popular culture/meme/nostalgia inspired), the desired room (optional) and a feed of the 20 most recently generated titles. This last part is key: rather than telling the model “don’t repeat yourself,” we hand it positive context; “here’s what already exists”, which language models handle far more reliably than negation constraints.&lt;/p&gt;

&lt;p&gt;The Amazon Bedrock Knowledge Base grounds the output in proven, family-friendly formats, while the session attributes steer it toward something fresh and specific. The result is ideas that feel handpicked and personalized, rather than randomly generated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sequence diagram for the creative idea generation process&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%2F0a9338g1poltf6qkjgp6.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%2F0a9338g1poltf6qkjgp6.png" width="800" height="466"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s take a closer look at the Lambda function for idea creation. As it is hundreds of line of code, I’ve included a simplified pseudo-code example below to illustrate my approach.&lt;/p&gt;

&lt;p&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;textarea&lt;/span&gt; &lt;span class="n"&gt;tabindex&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="n"&gt;aria&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;hidden&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="n"&gt;readonly&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;generate_creative_idea&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;custom_prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

  &lt;span class="mf"&gt;1.&lt;/span&gt; &lt;span class="n"&gt;READ&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="nf"&gt;vars &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AGENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AGENT_ALIAS_ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;missing&lt;/span&gt;

  &lt;span class="mf"&gt;2.&lt;/span&gt; &lt;span class="n"&gt;RESOLVE&lt;/span&gt; &lt;span class="nf"&gt;parameters &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mood&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;room&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;randomly&lt;/span&gt; &lt;span class="n"&gt;pick&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;toddlers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;preschoolers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;school&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tweens&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;given&lt;/span&gt;
     &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;randomly&lt;/span&gt; &lt;span class="n"&gt;pick&lt;/span&gt; &lt;span class="n"&gt;mood&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;naughty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pop&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;given&lt;/span&gt;
     &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nb"&gt;map&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;naughty&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mischievous&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;safe&lt;/span&gt; &lt;span class="n"&gt;alias&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;avoid&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="nb"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="mf"&gt;3.&lt;/span&gt; &lt;span class="n"&gt;BUILD&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="n"&gt;attributes&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;
     &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;always&lt;/span&gt; &lt;span class="n"&gt;include&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;customPrompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt;
     &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;conditionally&lt;/span&gt; &lt;span class="n"&gt;include&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;mood &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;safe&lt;/span&gt; &lt;span class="n"&gt;alias&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;room&lt;/span&gt;

  &lt;span class="mf"&gt;4.&lt;/span&gt; &lt;span class="n"&gt;FETCH&lt;/span&gt; &lt;span class="n"&gt;last&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="n"&gt;idea&lt;/span&gt; &lt;span class="n"&gt;titles&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;DB&lt;/span&gt;
     &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;recentIdeaTitles&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="nf"&gt;attributes &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;positive&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;prohibition&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="mf"&gt;5.&lt;/span&gt; &lt;span class="n"&gt;RETRY&lt;/span&gt; &lt;span class="nc"&gt;LOOP &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;up&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="n"&gt;attempts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exponential&lt;/span&gt; &lt;span class="n"&gt;backoff&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

     &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;BUILD&lt;/span&gt; &lt;span class="nb"&gt;input&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="n"&gt;via&lt;/span&gt; &lt;span class="nf"&gt;build_agent_input&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;adjusts&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="n"&gt;wording&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;previous&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="n"&gt;was&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;blocked&lt;/span&gt;

     &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;INVOKE&lt;/span&gt; &lt;span class="n"&gt;Bedrock&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;streaming&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="k"&gt;pass&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="n"&gt;attributes&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;input&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;

        &lt;span class="c1"&gt;# Actual Python snippet below
&lt;/span&gt;
        &lt;span class="c1"&gt;# Get agent configuration from environment
&lt;/span&gt;        &lt;span class="n"&gt;agent_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&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;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;agent_alias_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&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;AGENT_ALIAS_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;knowledge_base_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&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;KNOWLEDGE_BASE_ID&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;agent_id&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;agent_alias_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&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 not configured: AGENT_ID and AGENT_ALIAS_ID environment variables required&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Create Bedrock Agent Runtime client
&lt;/span&gt;        &lt;span class="n"&gt;bedrock_agent_runtime&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-agent-runtime&lt;/span&gt;&lt;span class="sh"&gt;'&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;eu-central-1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Retry configuration with exponential backoff
&lt;/span&gt;        &lt;span class="n"&gt;max_retries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
        &lt;span class="n"&gt;base_delay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="c1"&gt;# seconds
&lt;/span&gt;
        &lt;span class="c1"&gt;# Track if previous attempt was content-blocked (to adjust prompt on retry)
&lt;/span&gt;        &lt;span class="n"&gt;content_blocked_retry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;retry_attempt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_retries&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="c1"&gt;# Rebuild input text each attempt — adjusts prompt if previous was blocked
&lt;/span&gt;                &lt;span class="n"&gt;input_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;build_agent_input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;mood&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;safe_mood&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;custom_prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;custom_prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;room&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;room&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;content_blocked&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;content_blocked_retry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;recent_titles&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;recent_titles&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt;

                &lt;span class="n"&gt;agent_start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

                &lt;span class="c1"&gt;# Invoke agent with streaming response
&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;bedrock_agent_runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke_agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;agentId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;agent_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;agentAliasId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;agent_alias_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;sessionId&lt;/span&gt;&lt;span class="o"&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;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
                    &lt;span class="n"&gt;sessionState&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;sessionAttributes&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;session_attributes&lt;/span&gt;
                    &lt;span class="p"&gt;},&lt;/span&gt;
                    &lt;span class="n"&gt;inputText&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;input_text&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt;

                &lt;span class="c1"&gt;# Parse streaming response
&lt;/span&gt;                &lt;span class="n"&gt;idea_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse_agent_response&lt;/span&gt;&lt;span class="p"&gt;(&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;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;PARSE&lt;/span&gt; &lt;span class="n"&gt;streaming&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nf"&gt;idea_data &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

     &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;PUBLISH&lt;/span&gt; &lt;span class="nf"&gt;metrics &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="n"&gt;latency&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;KB&lt;/span&gt; &lt;span class="n"&gt;citations&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="n"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;SUCCESS&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt; &lt;span class="n"&gt;loop&lt;/span&gt;

     &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;ContentBlockedError&lt;/span&gt;
        &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;retries&lt;/span&gt; &lt;span class="n"&gt;remain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="n"&gt;content_blocked_retry&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;retry&lt;/span&gt;
        &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;raise&lt;/span&gt;

     &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;ThrottlingException&lt;/span&gt;
        &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;retries&lt;/span&gt; &lt;span class="n"&gt;remain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;retry&lt;/span&gt;
        &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;raise&lt;/span&gt;

     &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;ResourceNotFoundException&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;AccessDeniedException&lt;/span&gt;
        &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nf"&gt;immediately &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;no&lt;/span&gt; &lt;span class="n"&gt;retry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

     &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;generic&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt;
        &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;retries&lt;/span&gt; &lt;span class="n"&gt;remain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;retry&lt;/span&gt;
        &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;raise&lt;/span&gt;

  &lt;span class="mf"&gt;6.&lt;/span&gt; &lt;span class="n"&gt;WARN&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="n"&gt;generation&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;gt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="nf"&gt;s &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;near&lt;/span&gt; &lt;span class="n"&gt;Lambda&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="mf"&gt;7.&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="n"&gt;custom_prompt&lt;/span&gt; &lt;span class="n"&gt;given&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
     &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="n"&gt;how&lt;/span&gt; &lt;span class="n"&gt;well&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="n"&gt;incorporated&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;
     &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;warn&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;lt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;

  &lt;span class="mf"&gt;8.&lt;/span&gt; &lt;span class="n"&gt;BUILD&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
     &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;
     &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;mood &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mapped&lt;/span&gt; &lt;span class="n"&gt;back&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;safe&lt;/span&gt; &lt;span class="n"&gt;alias&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;room&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;materials_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="n"&gt;time_available&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;custom_prompt_score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kb_used&lt;/span&gt; &lt;span class="n"&gt;flag&lt;/span&gt;
     &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;generation_time_ms&lt;/span&gt;

  &lt;span class="mf"&gt;9.&lt;/span&gt; &lt;span class="n"&gt;RETURN&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;textarea&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&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;function&lt;/span&gt; &lt;span class="nf"&gt;generate_creative_idea&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;custom_prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

  &lt;span class="mf"&gt;1.&lt;/span&gt; &lt;span class="n"&gt;READ&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="nf"&gt;vars &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AGENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AGENT_ALIAS_ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;missing&lt;/span&gt;

  &lt;span class="mf"&gt;2.&lt;/span&gt; &lt;span class="n"&gt;RESOLVE&lt;/span&gt; &lt;span class="nf"&gt;parameters &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mood&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;room&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;randomly&lt;/span&gt; &lt;span class="n"&gt;pick&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;toddlers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;preschoolers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;school&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tweens&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;given&lt;/span&gt;
     &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;randomly&lt;/span&gt; &lt;span class="n"&gt;pick&lt;/span&gt; &lt;span class="n"&gt;mood&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;naughty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pop&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;given&lt;/span&gt;
     &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nb"&gt;map&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;naughty&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mischievous&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;safe&lt;/span&gt; &lt;span class="n"&gt;alias&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;avoid&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="nb"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="mf"&gt;3.&lt;/span&gt; &lt;span class="n"&gt;BUILD&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="n"&gt;attributes&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;
     &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;always&lt;/span&gt; &lt;span class="n"&gt;include&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;customPrompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt;
     &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;conditionally&lt;/span&gt; &lt;span class="n"&gt;include&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;mood &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;safe&lt;/span&gt; &lt;span class="n"&gt;alias&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;room&lt;/span&gt;

  &lt;span class="mf"&gt;4.&lt;/span&gt; &lt;span class="n"&gt;FETCH&lt;/span&gt; &lt;span class="n"&gt;last&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="n"&gt;idea&lt;/span&gt; &lt;span class="n"&gt;titles&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;DB&lt;/span&gt;
     &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;recentIdeaTitles&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="nf"&gt;attributes &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;positive&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;prohibition&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="mf"&gt;5.&lt;/span&gt; &lt;span class="n"&gt;RETRY&lt;/span&gt; &lt;span class="nc"&gt;LOOP &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;up&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="n"&gt;attempts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exponential&lt;/span&gt; &lt;span class="n"&gt;backoff&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

     &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;BUILD&lt;/span&gt; &lt;span class="nb"&gt;input&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="n"&gt;via&lt;/span&gt; &lt;span class="nf"&gt;build_agent_input&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;adjusts&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="n"&gt;wording&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;previous&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="n"&gt;was&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;blocked&lt;/span&gt;

     &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;INVOKE&lt;/span&gt; &lt;span class="n"&gt;Bedrock&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;streaming&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="k"&gt;pass&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="n"&gt;attributes&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;input&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;

        &lt;span class="c1"&gt;# Actual Python snippet below
&lt;/span&gt;
        &lt;span class="c1"&gt;# Get agent configuration from environment
&lt;/span&gt;        &lt;span class="n"&gt;agent_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&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;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;agent_alias_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&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;AGENT_ALIAS_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;knowledge_base_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&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;KNOWLEDGE_BASE_ID&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;agent_id&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;agent_alias_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&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 not configured: AGENT_ID and AGENT_ALIAS_ID environment variables required&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Create Bedrock Agent Runtime client
&lt;/span&gt;        &lt;span class="n"&gt;bedrock_agent_runtime&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-agent-runtime&lt;/span&gt;&lt;span class="sh"&gt;'&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;eu-central-1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Retry configuration with exponential backoff
&lt;/span&gt;        &lt;span class="n"&gt;max_retries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
        &lt;span class="n"&gt;base_delay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="c1"&gt;# seconds
&lt;/span&gt;
        &lt;span class="c1"&gt;# Track if previous attempt was content-blocked (to adjust prompt on retry)
&lt;/span&gt;        &lt;span class="n"&gt;content_blocked_retry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;retry_attempt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_retries&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="c1"&gt;# Rebuild input text each attempt — adjusts prompt if previous was blocked
&lt;/span&gt;                &lt;span class="n"&gt;input_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;build_agent_input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;mood&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;safe_mood&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;custom_prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;custom_prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;room&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;room&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;content_blocked&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;content_blocked_retry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;recent_titles&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;recent_titles&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt;

                &lt;span class="n"&gt;agent_start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

                &lt;span class="c1"&gt;# Invoke agent with streaming response
&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;bedrock_agent_runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke_agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;agentId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;agent_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;agentAliasId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;agent_alias_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;sessionId&lt;/span&gt;&lt;span class="o"&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;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
                    &lt;span class="n"&gt;sessionState&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;sessionAttributes&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;session_attributes&lt;/span&gt;
                    &lt;span class="p"&gt;},&lt;/span&gt;
                    &lt;span class="n"&gt;inputText&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;input_text&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt;

                &lt;span class="c1"&gt;# Parse streaming response
&lt;/span&gt;                &lt;span class="n"&gt;idea_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse_agent_response&lt;/span&gt;&lt;span class="p"&gt;(&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;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;PARSE&lt;/span&gt; &lt;span class="n"&gt;streaming&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nf"&gt;idea_data &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

     &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;PUBLISH&lt;/span&gt; &lt;span class="nf"&gt;metrics &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="n"&gt;latency&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;KB&lt;/span&gt; &lt;span class="n"&gt;citations&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="n"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;SUCCESS&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt; &lt;span class="n"&gt;loop&lt;/span&gt;

     &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;ContentBlockedError&lt;/span&gt;
        &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;retries&lt;/span&gt; &lt;span class="n"&gt;remain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="n"&gt;content_blocked_retry&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;retry&lt;/span&gt;
        &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;raise&lt;/span&gt;

     &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;ThrottlingException&lt;/span&gt;
        &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;retries&lt;/span&gt; &lt;span class="n"&gt;remain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;retry&lt;/span&gt;
        &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;raise&lt;/span&gt;

     &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;ResourceNotFoundException&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;AccessDeniedException&lt;/span&gt;
        &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nf"&gt;immediately &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;no&lt;/span&gt; &lt;span class="n"&gt;retry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

     &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;generic&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt;
        &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;retries&lt;/span&gt; &lt;span class="n"&gt;remain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;retry&lt;/span&gt;
        &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;raise&lt;/span&gt;

  &lt;span class="mf"&gt;6.&lt;/span&gt; &lt;span class="n"&gt;WARN&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="n"&gt;generation&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="nf"&gt;s &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;near&lt;/span&gt; &lt;span class="n"&gt;Lambda&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="mf"&gt;7.&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="n"&gt;custom_prompt&lt;/span&gt; &lt;span class="n"&gt;given&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
     &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="n"&gt;how&lt;/span&gt; &lt;span class="n"&gt;well&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="n"&gt;incorporated&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;
     &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;warn&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;

  &lt;span class="mf"&gt;8.&lt;/span&gt; &lt;span class="n"&gt;BUILD&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
     &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;
     &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;mood &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mapped&lt;/span&gt; &lt;span class="n"&gt;back&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;safe&lt;/span&gt; &lt;span class="n"&gt;alias&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;room&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;materials_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="n"&gt;time_available&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;custom_prompt_score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kb_used&lt;/span&gt; &lt;span class="n"&gt;flag&lt;/span&gt;
     &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;generation_time_ms&lt;/span&gt;

  &lt;span class="mf"&gt;9.&lt;/span&gt; &lt;span class="n"&gt;RETURN&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Infrastructure-as-Code for the Amazon Bedrock Agent implementation is handled with &lt;a href="https://docs.aws.amazon.com/cdk/v2/guide/work-with-cdk-typescript.html" rel="noopener noreferrer"&gt;AWS Cloud Development Kit in Typescript&lt;/a&gt;, with specific and targeted agent instructions for the use-case. Tuning the prompts along with the Knowledge Base context was where I spent a lot of time to ensure relevant responses.&lt;/p&gt;

&lt;p&gt;As there are many things that can go wrong in this process, content blocking by &lt;a href="https://docs.aws.amazon.com/bedrock/latest/userguide/guardrails.html" rel="noopener noreferrer"&gt;Amazon Bedrock Guardrails&lt;/a&gt; or safety filter in the language models themselves and Amazon Bedrock &lt;a href="https://docs.aws.amazon.com/bedrock/latest/userguide/capacity-limits-cost-optimization.html" rel="noopener noreferrer"&gt;capacity constraints&lt;/a&gt; in certain regions, it is vital to remember the Well-Architected principle of &lt;strong&gt;&lt;a href="https://docs.aws.amazon.com/wellarchitected/latest/framework/rel_mitigate_interaction_failure_limit_retries.html" rel="noopener noreferrer"&gt;REL05-BP03 Control and limit retry calls&lt;/a&gt;&lt;/strong&gt; and implement proper retry and error handling.&lt;/p&gt;

&lt;p&gt;Below is an example of the implemented logic for idea generation.&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%2Fgwgfj6t10h8lcgrv3rgl.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%2Fgwgfj6t10h8lcgrv3rgl.png" width="800" height="806"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons learned
&lt;/h2&gt;

&lt;p&gt;As the concept of the Naughty/Mischievous Elf is creative, but with very specific boundaries about what’s acceptable and not, also taking into account cultural differences across countries, it was challenging to get the platform to produce relevant results in the beginning. As with most projects involving Artificial Intelligence, &lt;em&gt;scoping&lt;/em&gt;, &lt;em&gt;context and iterative testing and validation&lt;/em&gt; makes all the difference. Below I share my top five lessons learned.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lesson 1: Construct prompts according to model provider guidance
&lt;/h3&gt;

&lt;p&gt;Different models may expect prompts to be designed and structured in a particular way. Amazon Nova models and Anthropic Claude models expect input in different formats for optimal and relevant results. To get the results you’re after, check the model provider’s guidance. Keep the prompts version controlled so that you know what changed when results suddenly deviate. Test and benchmark until you’re happy. &lt;a href="https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-management.html" rel="noopener noreferrer"&gt;Amazon Bedrock Prompt Management&lt;/a&gt; may also be something to consider for more static prompts.&lt;/p&gt;

&lt;p&gt;For Amazon Nova models see &lt;a href="https://docs.aws.amazon.com/nova/latest/userguide/prompting-precision.html" rel="noopener noreferrer"&gt;User Guide: Creating precise prompts&lt;/a&gt;. For Anthropic Claude models such as Opus, Sonnet or Haiku see &lt;a href="https://platform.claude.com/docs/en/build-with-claude/prompt-engineering/claude-prompting-best-practices" rel="noopener noreferrer"&gt;Claude API Docs: Prompting best practices&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lesson 2: Prompt engineering alone wasn’t enough, RAG changed everything
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt; : Initial prompts produced generic, uninspired elf ideas. Despite adding constraints, few-shot examples, and negative prompts, the process kept generating the same 3-5 ideas repeatedly. Most ideas agitated towards spillage, flour mess, toilet paper and so on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What didn’t work&lt;/strong&gt; :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Adding more constraints to the prompt logic.&lt;/li&gt;
&lt;li&gt;Including few-shot examples of “good” ideas.&lt;/li&gt;
&lt;li&gt;Negative constraints (“avoid messy setups”, “no flour ideas”) – This amplified it and led to even more of them!&lt;/li&gt;
&lt;li&gt;Temperature adjustments (higher = more random, not more creative).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What actually worked: Retrieval Augmented Generation&lt;/strong&gt; :&lt;/p&gt;

&lt;p&gt;To help the model with more context I sourced ~150 unique ideas from other elf idea blogs and Pinterest boards and set up an &lt;a href="https://docs.aws.amazon.com/bedrock/latest/userguide/knowledge-base.html" rel="noopener noreferrer"&gt;Amazon Bedrock Knowledge Base&lt;/a&gt; with these ideas as inspiration (not to copy, but to provide a more comprehensive understanding of the creative space).&lt;/p&gt;

&lt;p&gt;It had to be cost efficient, so Amazon OpenSearch was ruled out in favour of Amazon S3 Vectors. The agent now retrieves relevant examples before generating, dramatically improving variety and relevancy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The feedback loop&lt;/strong&gt; :&lt;/p&gt;

&lt;p&gt;In the final part of the orchestration flow newly generated ideas are added back to the knowledge base, which creates a growing corpus of unique ideas. The more ideas generated, the more diverse future generations become. After 150+ ideas in the library, repetition dropped from ~30% to &amp;lt;5%.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Outcome&lt;/strong&gt; : When I provided relevant &lt;em&gt;Perceive context&lt;/em&gt; with Retrieval Augment Generation (RAG) and Amazon Bedrock Knowledge Bases, RAG expanded the creative space. This was a major breakthrough.&lt;/p&gt;

&lt;p&gt;The language models themselves are supposed to be generic, based on the material they’re trained on. Prompt engineering on specialized or generic language models will only take you so far. My approach is to use the language models for &lt;em&gt;reasoning&lt;/em&gt;, not the actual &lt;em&gt;content&lt;/em&gt; (which also has a cut-off date after the model is trained and published). This is what most people don’t get. RAG is also faster and more cost-efficient than fine-tuning a model.&lt;/p&gt;

&lt;p&gt;To illustrate the concept of Perceive, Reason and Act, the &lt;a href="https://docs.aws.amazon.com/prescriptive-guidance/latest/agentic-ai-foundations/introduction.html" rel="noopener noreferrer"&gt;AWS Prescriptive Guidance: Foundations of agentic AI on AWS&lt;/a&gt; explains:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“At the core of every software agent is a cognitive cycle that is often described as the perceive, reason, act loop. This process is illustrated in the following diagram. It defines how agents operate autonomously in dynamic environments.”.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;_ &lt;strong&gt;Perceive&lt;/strong&gt; : Agents gather information (for example, events, sensor inputs, or API signals) from the environment and update their internal state or beliefs._&lt;/li&gt;
&lt;li&gt;_ &lt;strong&gt;Reason&lt;/strong&gt; : Agents analyze current beliefs, goals, and contextual knowledge by using a plan library or logic system. This process might involve goal prioritization, conflict resolution, or intention selection._&lt;/li&gt;
&lt;li&gt;_ &lt;strong&gt;Act&lt;/strong&gt; : Agents select and execute actions that move them closer to achieving their delegated goals._&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%2Fo0np1fy56u95jeeh8ydx.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%2Fo0np1fy56u95jeeh8ydx.png" width="799" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Reference: &lt;a href="https://docs.aws.amazon.com/prescriptive-guidance/latest/agentic-ai-foundations/perceive-reason-act.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/prescriptive-guidance/latest/agentic-ai-foundations/perceive-reason-act.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AWS Prescriptive Guidance: Generative AI agents: replacing symbolic logic with LLMs&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“The following diagram illustrates how large language models (LLMs) now serve as a flexible and intelligent cognitive core for software agents. In contrast to traditional symbolic logic systems, which rely on static plan libraries and hand-coded rules, LLMs enable adaptive reasoning, contextual planning, and dynamic tool use, which transform how agents perceive, reason, and act.”&lt;/em&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%2Fknysnl8hqkdfw2ouwhzx.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%2Fknysnl8hqkdfw2ouwhzx.png" width="800" height="401"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Reference: &lt;a href="https://docs.aws.amazon.com/prescriptive-guidance/latest/agentic-ai-foundations/generative-ai-agents.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/prescriptive-guidance/latest/agentic-ai-foundations/generative-ai-agents.html&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Lesson 3: LLMs doesn’t follow “don’t do X” – Use positive context instead
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt; : After a few hundred ideas, the system started generating duplicates. Two ideas generated 32 minutes apart were essentially the same concept — “elf swaps everyone’s phone contact names” — despite the Amazon Bedrock Knowledge Base similarity check that was supposed to catch this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why the Knowledge Base similarity check wasn’t enough&lt;/strong&gt; : The check fired &lt;em&gt;after&lt;/em&gt; generation. By then, the Amazon Bedrock call had already been made and billed. Worse, the first generated idea got added back to the Amazon Bedrock Knowledge Base, so the second generation retrieves it as a &lt;em&gt;reference&lt;/em&gt;, and the model riffs on it rather than diverging from it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The instinct and why it doesn’t work&lt;/strong&gt; : The obvious fix is to pass recent idea titles to the agent with a “don’t repeat these” instruction. This is a well-documented failure mode: LLMs are poor at negation constraints. “Don’t generate X” makes X more salient in the model’s attention, not less. The model acknowledges the constraint, then gravitates toward the same concepts anyway.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What actually works: positive context framing&lt;/strong&gt; : According to &lt;a href="https://platform.claude.com/docs/en/build-with-claude/prompt-engineering/claude-prompting-best-practices#add-context-to-improve-performance" rel="noopener noreferrer"&gt;Claude API Docs: Add context to improve performance:&lt;/a&gt; &lt;em&gt;Providing context or motivation behind your instructions, such as explaining to Claude why such behavior is important, can help Claude better understand your goals and deliver more targeted responses.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;_ &lt;strong&gt;Less effective:&lt;/strong&gt; NEVER use ellipses_&lt;/p&gt;

&lt;p&gt;_ &lt;strong&gt;More effective:&lt;/strong&gt; Your response will be read aloud by a text-to-speech engine, so never use ellipses since the text-to-speech engine will not know how to pronounce them._&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Taking this into consideration, instead of “avoid these ideas”, I framed it as “here’s what already exists, explore a different domain”:&lt;/p&gt;

&lt;p&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;textarea&lt;/span&gt; &lt;span class="n"&gt;tabindex&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="n"&gt;aria&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;hidden&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="n"&gt;readonly&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="c1"&gt;# &amp;lt;img src="https://s.w.org/images/core/emoji/17.0.2/72x72/274c.png" alt="❌" style="height: 1em; max-height: 1em;"&amp;gt; Negation framing — doesn't work reliably
&lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Do NOT generate ideas similar to these recent ones:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;recent_titles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&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; - &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;title&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="c1"&gt;# &amp;lt;img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" style="height: 1em; max-height: 1em;"&amp;gt; Positive context framing — works with how LLMs process context
&lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;STEP 3: These ideas have been generated recently — create something in a DIFFERENT domain:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;recent_titles&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&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; - &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;title&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="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;textarea&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&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;# ❌ Negation framing — doesn't work reliably
&lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Do NOT generate ideas similar to these recent ones:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;recent_titles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&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; - &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;title&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="c1"&gt;# ✅ Positive context framing — works with how LLMs process context
&lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;STEP 3: These ideas have been generated recently — create something in a DIFFERENT domain:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;recent_titles&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&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; - &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;title&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;The model uses the list as &lt;em&gt;context about what’s already covered&lt;/em&gt; and naturally diverges. It’s the same information, but framed as situational awareness rather than a prohibition.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The implementation&lt;/strong&gt; : Before invoking the agent, &lt;code&gt;fetch_recent_idea_titles()&lt;/code&gt; queries DynamoDB’s &lt;code&gt;StatusIndex&lt;/code&gt; for the 20 most recent active ideas (newest first, projection on &lt;code&gt;translations&lt;/code&gt; only for efficiency). The titles are joined as a pipe-separated string in &lt;code&gt;sessionAttributes['recentIdeaTitles']&lt;/code&gt; and also injected into the prompt as step 3. Fails are open, a DynamoDB error never blocks generation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Defense in depth&lt;/strong&gt; : The pre-generation context reduces the probability of duplicates. The post-generation &lt;code&gt;check_similar_ideas()&lt;/code&gt; in catches anything that slips through, and the Step Function retries with &lt;code&gt;attempt=2&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key insight&lt;/strong&gt; : LLMs process context, not rules. “Here’s what exists” is context. “Don’t do X” is a rule. Context wins.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lesson 4: Multilingual translation challenges (Norwegian and Swedish)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt; : Spanish translations were okay, but Norwegian and Swedish translations were awkward and unnatural. Results with Amazon Nova Pro/Lite models were not acceptable out of the box. Amazon Translate was even worse – too literal, missing cultural nuance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Approach&lt;/strong&gt; : I tested multiple models: &lt;a href="https://docs.aws.amazon.com/ai/responsible-ai/nova-micro-lite-pro/overview.html" rel="noopener noreferrer"&gt;Amazon Nova Pro,&lt;/a&gt; &lt;a href="https://docs.aws.amazon.com/ai/responsible-ai/nova-2-lite/overview.html" rel="noopener noreferrer"&gt;Amazon Nova 2 Lite&lt;/a&gt;, &lt;a href="https://www.google.com/url?sa=t&amp;amp;source=web&amp;amp;rct=j&amp;amp;opi=89978449&amp;amp;url=https://aws.amazon.com/translate/&amp;amp;ved=2ahUKEwinvPDG146TAxU-ORAIHaq_ACsQFnoECCAQAQ&amp;amp;usg=AOvVaw3bunk5vgystAnfslr_fvUO" rel="noopener noreferrer"&gt;Amazon Translate&lt;/a&gt;, &lt;a href="https://www.anthropic.com/claude/haiku" rel="noopener noreferrer"&gt;Anthropic Claude Haiku&lt;/a&gt; and &lt;a href="https://www.anthropic.com/claude/sonnet" rel="noopener noreferrer"&gt;Anthropic Claude Sonnet&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;From the AWS AI Service Card overview on Amazon Nova Models – Language:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Amazon Nova models are released as generally available for English, German, French, Spanish, Italian, Portuguese, Japanese, Hindi, and Arabic. Amazon Nova model’s guardrails are intended for use in the aforementioned languages only, however Amazon Nova models are trained and will attempt to provide completions in up to 200 languages. In use cases beyond the languages identified above, customers should carefully check completions for effectiveness, including safety.&lt;/p&gt;

&lt;p&gt;Reference: &lt;a href="https://docs.aws.amazon.com/ai/responsible-ai/nova-micro-lite-pro/overview.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/ai/responsible-ai/nova-micro-lite-pro/overview.html&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;From Anthropic Claude API Docs: Multilingual support:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Claude excels at tasks across multiple languages, maintaining strong cross-lingual performance relative to English.&lt;/p&gt;

&lt;p&gt;Claude demonstrates robust multilingual capabilities, with particularly strong performance in zero-shot tasks across languages. The model maintains consistent relative performance across both widely-spoken and lower-resource languages, making it a reliable choice for multilingual applications.&lt;/p&gt;

&lt;p&gt;Reference: &lt;a href="https://platform.claude.com/docs/en/build-with-claude/multilingual-support" rel="noopener noreferrer"&gt;https://platform.claude.com/docs/en/build-with-claude/multilingual-support&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A two-pass approach with Claude Haiku produced the best results at a reasonable price point:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;First pass: Direct translation&lt;/li&gt;
&lt;li&gt;Second pass: “Refine this Norwegian text to sound natural, as if written by a native speaker”&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Outcome&lt;/strong&gt; : Natural Norwegian and Swedish like a native speaker would write it. Two-pass added ~2-3 seconds of latency, but it was definitely worth it. Claude’s broader multilingual training data made the difference, and the most lightweight option with Haiku did the trick, at the best performance and price.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lesson 5: AWS Step Functions vs. Amazon Bedrock Agents – Why I chose deterministic orchestration
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt; : The first version of the idea generation workflow was a mess. Too many fine-grained states, no parallel processing, and a failed experiment with Amazon Bedrock Agents that added 40-70 seconds of latency I couldn’t explain at first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First iteration: The Amazon Bedrock Agent experiment&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In the initial iteration the orchestration layer was based on Amazon Bedrock Agents. The appeal was obvious; natural language instructions, dynamic tool selection, no explicit state machine to maintain. In theory, you describe what you want and the agent figures out the steps.&lt;/p&gt;

&lt;p&gt;In practice, it was the wrong tool for this job. The agent’s reasoning loop – deciding which tool to call, in what order, with what parameters – added ~45-70 seconds of overhead on top of the actual model invocations. For a workflow where the involved steps are known in advance (generate idea → translate → generate image → store ++), that reasoning overhead provides zero value. You’re paying for flexibility you don’t need.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next iteration: AWS Step Functions&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Defining all steps in the process in an AWS Step Functions State Machine was relatively straight forward, and i set up the idea generation step is a separate AWS Lambda function, invoking the Bedrock Agent for the creative reasoning process only. This cut the processing time in half and also provided better visibility into the end-to-end workflow.&lt;/p&gt;

&lt;h4&gt;
  
  
  Comparison of orchestration workflow alternatives
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;AWS Step Functions&lt;/th&gt;
&lt;th&gt;Amazon Bedrock Agents&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Control &amp;amp; predictability&lt;/td&gt;
&lt;td&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%2F8dwxu3ucolcmolsqelqb.png" alt="✅" width="72" height="72"&gt; Deterministic, explicit state transitions&lt;/td&gt;
&lt;td&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%2Ftco2892mv8tzss0j95hn.png" alt="⚠" width="72" height="72"&gt; AI-driven, non-deterministic paths&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Latency&lt;/td&gt;
&lt;td&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%2F8dwxu3ucolcmolsqelqb.png" alt="✅" width="72" height="72"&gt; ~15-25s for idea generation&lt;/td&gt;
&lt;td&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%2Fiwpv54tbh3qlhywfzxuv.png" alt="❌" width="72" height="72"&gt; ~45-90s due to reasoning overhead&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost model&lt;/td&gt;
&lt;td&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%2F8dwxu3ucolcmolsqelqb.png" alt="✅" width="72" height="72"&gt; Pay per state transition&lt;/td&gt;
&lt;td&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%2Ftco2892mv8tzss0j95hn.png" alt="⚠" width="72" height="72"&gt; Pay per token + reasoning tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flexibility&lt;/td&gt;
&lt;td&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%2Ftco2892mv8tzss0j95hn.png" alt="⚠" width="72" height="72"&gt; Requires code changes for new flows&lt;/td&gt;
&lt;td&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%2F8dwxu3ucolcmolsqelqb.png" alt="✅" width="72" height="72"&gt; Natural language instructions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Operational complexity&lt;/td&gt;
&lt;td&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%2F8dwxu3ucolcmolsqelqb.png" alt="✅" width="72" height="72"&gt; Visual debugging, built-in retries&lt;/td&gt;
&lt;td&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%2Ftco2892mv8tzss0j95hn.png" alt="⚠" width="72" height="72"&gt; Black-box reasoning, harder to debug&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best for&lt;/td&gt;
&lt;td&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%2F8dwxu3ucolcmolsqelqb.png" alt="✅" width="72" height="72"&gt; Well-defined, latency-sensitive workflows&lt;/td&gt;
&lt;td&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%2F8dwxu3ucolcmolsqelqb.png" alt="✅" width="72" height="72"&gt; Dynamic, exploratory tasks&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The latency difference alone was disqualifying. Parents using the site at 11 PM won’t have the patience to wait two minutes seconds for an idea. But the operational complexity gap matters just as much in practice; when an Amazon Bedrock Agent fails, you get a vague error from the reasoning loop (out of the box) and you have to add more logging for debugging capabilities. When an AWS Step Functions state fails, you see exactly which state, with the full input/output, on a visual timeline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Experiences with AWS Step Functions orchestration&lt;/strong&gt; :&lt;/p&gt;

&lt;p&gt;The initial Step Functions workflow had its own problems — too many fine-grained states that should have been a single Lambda call, and no parallel processing. Consolidating related operations and adding parallel branches for translation and image generation brought execution time from ~45s down to ~20s.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Consolidated related operations into single Lambda functions (fewer state transitions, less overhead)&lt;/li&gt;
&lt;li&gt;Used parallel branches for translation and image generation — they’re independent, so there’s no reason to run them sequentially&lt;/li&gt;
&lt;li&gt;Added explicit error handling states with retry logic instead of relying on default behavior&lt;/li&gt;
&lt;li&gt;Implemented cost limit checks at the start to fail fast before any expensive model invocations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Key takeaway:&lt;/strong&gt; Use AWS Step Functions when the workflow is well-defined and the steps are known in advance. Use Amazon Bedrock Agents when you genuinely need dynamic reasoning – when the agent needs to decide &lt;em&gt;which&lt;/em&gt; tools to call based on context, or when the workflow varies significantly between executions. For content generation pipelines with fixed steps, AWS Step Functions wins on every dimension that matters: latency, cost, debuggability, and predictability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Outcome&lt;/strong&gt; : Overall execution time ~90-60 seconds → ~30 seconds. The AWS Step Functions visual debugger has saved me hours of troubleshooting – being able to replay a failed execution with the exact input/output at each state is invaluable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Building this site just for fun to push the boundaries of creative capabilities with Generative AI on AWS has been really cool and inspiring. I’ve learned a lot during the process which will also be extremely valuable when implementing customer solutions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key takeaways
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;As Generative and Agentic AI technology matures, more and more use-cases emerge&lt;/strong&gt;  – But, to meet expectations, solution design requires careful orchestration, model benchmarking and output quality assurance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model capabilities are also developing fast&lt;/strong&gt; – Stay up to date on the recent advances and availability of new model versions to experience even more accurate and high quality results.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model selection requires empirical testing&lt;/strong&gt;  – benchmarks don’t tell the whole story, especially for sub-major languages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS Step Functions beats Amazon Bedrock Agents for deterministic workflows&lt;/strong&gt;  – the latency difference could be significant (~30s vs ~60-120s in this case). Use Agent orchestration for non-deterministic scenarios or where you need to handle unknown edge cases. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Content safety is solvable&lt;/strong&gt;  – Amazon Bedrock Guardrails provides enterprise-grade filtering with minimal latency impact. Also, some model providers have built-in content safety filters. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Serverless scales effortlessly&lt;/strong&gt;  – the architecture handles traffic spikes during the Christmas season without intervention.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spec-driven development with Kiro boost innovation&lt;/strong&gt; – Leveraging Kiro along with a maturing base of agent/project steering definitions, MCP servers and subagents really amplified development velocity and learning outcomes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Future enhancements
&lt;/h3&gt;

&lt;p&gt;Some ideas currently in the backlog:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User accounts for saving favorite ideas and tracking which ones you’ve used.&lt;/li&gt;
&lt;li&gt;Personalization based on past preferences and household setup.&lt;/li&gt;
&lt;li&gt;December planning mode: Generate a full month of ideas in one go – no more nightly scrambling.&lt;/li&gt;
&lt;li&gt;Weekly elf calendar: Get a curated week of ideas that build on each other (storyline mode).&lt;/li&gt;
&lt;li&gt;Difficulty progression: Start simple in early December, escalate to elaborate setups as Christmas approaches.&lt;/li&gt;
&lt;li&gt;Integration with smart home devices (imagine the elf controlling your speakers and Christmas tree lights)!&lt;/li&gt;
&lt;li&gt;Collaborative features: Share your elf calendar with your partner so you can take turns.&lt;/li&gt;
&lt;li&gt;Portal support for additional languages (German, French).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Try it out yourself!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Visit &lt;a href="https://naughtyelfideas.com/" rel="noopener noreferrer"&gt;naughtyelfideas.com&lt;/a&gt; to see it in action and generate your own unique elf idea.&lt;/p&gt;

&lt;p&gt;Let me know what you think, feedback is welcome!&lt;/p&gt;

&lt;p&gt;The post &lt;a href="https://hedrange.com/2026/03/07/generative-ai-for-creativity-lessons-learned-from-building-a-christmas-elf-idea-platform-with-amazon-bedrock/" rel="noopener noreferrer"&gt;Generative AI for creativity: Lessons learned from building a Christmas Elf idea platform with Amazon Bedrock&lt;/a&gt; first appeared on &lt;a href="https://hedrange.com/" rel="noopener noreferrer"&gt;Håkon Eriksen Drange&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>articles</category>
      <category>aws</category>
      <category>bedrock</category>
      <category>generativeai</category>
    </item>
    <item>
      <title>AWS Immersion Day talk: Web Application Firewall and AWS Shield Advanced</title>
      <dc:creator>Håkon Eriksen Drange</dc:creator>
      <pubDate>Tue, 25 Nov 2025 20:17:00 +0000</pubDate>
      <link>https://dev.to/haakoned/aws-immersion-day-talk-web-application-firewall-and-aws-shield-advanced-38fk</link>
      <guid>https://dev.to/haakoned/aws-immersion-day-talk-web-application-firewall-and-aws-shield-advanced-38fk</guid>
      <description>&lt;p&gt;I was invited by AWS Norway to contribute to their AWS Immersion Day on Zero Trust which took place on Tuesday November 25th 2025.&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%2F1tbidmt2qdi6t6i7vrli.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%2F1tbidmt2qdi6t6i7vrli.png" width="648" height="393"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Elshan Hasanov from AWS started with an introduction to the concept of Zero Trust and how Amazon Verified Access and Amazon VPC Lattice can be used for the purpose. Next up I presented approaches for application and infrastructure security with AWS Web Application Firewall and AWS Shield Advanced.&lt;/p&gt;

&lt;p&gt;You can find the slides below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://hedrange.com/wp-content/uploads/2025/12/2025-11-25-aws-immersion-day-zero-trust-waf-shield-haakon-eriksen-drange.pdf" rel="noopener noreferrer"&gt;2025-11-25-aws-immersion-day-zero-trust-waf-shield-haakon-eriksen-drange&lt;/a&gt;&lt;a href="https://hedrange.com/wp-content/uploads/2025/12/2025-11-25-aws-immersion-day-zero-trust-waf-shield-haakon-eriksen-drange.pdf" rel="noopener noreferrer"&gt;Download&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;About the event&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
In today’s dynamic and distributed cloud environments, traditional perimeter security alone is no longer suﬃcient to protect against advanced threats. This session explores the paradigm shift towards Zero Trust architecture and its implementation within AWS ecosystems, covering the use of AWS services and features to adopt Zero Trust principles alongside traditional security approaches for fine-grained access control, network segmentation, secure data access, and logging. Join us to learn how to transition to a more secure, resilient, and comprehensive security approach tailored for your AWS environment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to Expect from the Event&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
This session and hands-on workshop provides an in-depth exploration of Zero Trust security principles and their practical application within AWS environments using AWS Verified Access and VPC Lattice, complemented by modern perimeter security strategies including WAF and firewall inspection.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Core Focus Areas&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Identity-aware, least-privilege access for both human users and microservices&lt;/li&gt;
&lt;li&gt;Integration of Zero Trust with strategic perimeter controls including WAF&lt;/li&gt;
&lt;li&gt;Practical implementation using AWS Verified Access and Amazon VPC Lattice&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Hands-on Components&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Configure AWS Verified Access for secure remote user access&lt;/li&gt;
&lt;li&gt;Implement Amazon VPC Lattice for service-to-service communication&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Original AWS Experience North event page:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://hedrange.com/wp-content/uploads/2025/12/2025-11-25-zero-trust-event-page.pdf" rel="noopener noreferrer"&gt;2025-11-25-zero-trust-event-page&lt;/a&gt;&lt;a href="https://hedrange.com/wp-content/uploads/2025/12/2025-11-25-zero-trust-event-page.pdf" rel="noopener noreferrer"&gt;Download&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The post &lt;a href="https://hedrange.com/2025/11/25/aws-immersion-day-talk-web-application-firewall-and-aws-shield-advanced/" rel="noopener noreferrer"&gt;AWS Immersion Day talk: Web Application Firewall and AWS Shield Advanced&lt;/a&gt; first appeared on &lt;a href="https://hedrange.com/" rel="noopener noreferrer"&gt;Håkon Eriksen Drange&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>articles</category>
    </item>
    <item>
      <title>AWS User Group Oslo talk: From Vibe Coding to Spec-Driven Development with Kiro</title>
      <dc:creator>Håkon Eriksen Drange</dc:creator>
      <pubDate>Wed, 19 Nov 2025 13:39:13 +0000</pubDate>
      <link>https://dev.to/haakoned/aws-user-group-oslo-talk-from-vibe-coding-to-spec-driven-development-with-kiro-3n7p</link>
      <guid>https://dev.to/haakoned/aws-user-group-oslo-talk-from-vibe-coding-to-spec-driven-development-with-kiro-3n7p</guid>
      <description>&lt;p&gt;On &lt;a href="https://www.meetup.com/aws-user-group-norway/events/311318914/" rel="noopener noreferrer"&gt;Tuesday November 18th at the AWS User Group Oslo Meetup&lt;/a&gt; I shared my views on why builders should pivot from Vibe Coding to Spec-Driven Development with Kiro.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Highlights&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How AI assistants are transforming the Software Development Lifecycle (all code, not only frontend and backend, Infrastructure-as-Code as well)&lt;/li&gt;
&lt;li&gt;Introduction to Vibe Coding, the good and the not so good parts&lt;/li&gt;
&lt;li&gt;How traditional and established software craftmanship is becoming more relevant than ever&lt;/li&gt;
&lt;li&gt;How Kiro and the Spec-Driven Development workflow helps bring more structure, quality and speed&lt;/li&gt;
&lt;li&gt;A practical demonstration; adding a new feature to a serverless weather forecast application, deployed on AWS with Terraform and GitHub Actions&lt;/li&gt;
&lt;li&gt;Best practices for specs, agent steering context, MCP and token optimization based on my experience&lt;/li&gt;
&lt;li&gt;Predictions for the future&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can find the slides below. Thanks to AWS Community Builders and the Amazon Kiro team for valuable insights and supporting material.&lt;/p&gt;

&lt;p&gt;For a detailed walk-through check out &lt;a href="https://hedrange.com/2025/08/11/how-to-use-kiro-for-ai-assisted-spec-driven-development/" rel="noopener noreferrer"&gt;https://hedrange.com/2025/08/11/how-to-use-kiro-for-ai-assisted-spec-driven-development/&lt;/a&gt; .&lt;/p&gt;

&lt;p&gt;&lt;a href="https://hedrange.com/wp-content/uploads/2025/11/2025-11-18-aws-user-group-oslo-from-vibe-coding-to-spec-driven-development-with-kiro.pdf" rel="noopener noreferrer"&gt;2025-11-18-aws-user-group-oslo-from-vibe-coding-to-spec-driven-development-with-kiro&lt;/a&gt;&lt;a href="https://hedrange.com/wp-content/uploads/2025/11/2025-11-18-aws-user-group-oslo-from-vibe-coding-to-spec-driven-development-with-kiro.pdf" rel="noopener noreferrer"&gt;Download&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%2Fcph4d6sbalserxv11o0m.webp" 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%2Fcph4d6sbalserxv11o0m.webp" width="800" height="602"&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%2Frgmxkpdarbxt4l6mhigz.jpeg" 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%2Frgmxkpdarbxt4l6mhigz.jpeg" width="800" height="450"&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%2Fjl8d5h0c4v3e0koh885o.jpeg" 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%2Fjl8d5h0c4v3e0koh885o.jpeg" width="800" height="450"&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%2Fcyoh86jvgbcglqz3zqoy.webp" 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%2Fcyoh86jvgbcglqz3zqoy.webp" width="768" height="1024"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Additional resources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://kiro.dev/" rel="noopener noreferrer"&gt;https://kiro.dev/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kiro.dev/blog/general-availability/" rel="noopener noreferrer"&gt;https://kiro.dev/blog/general-availability/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The post &lt;a href="https://hedrange.com/2025/11/19/aws-user-group-oslo-talk-going-from-vibe-coding-to-spec-driven-development-with-kiro/" rel="noopener noreferrer"&gt;AWS User Group Oslo talk: From Vibe Coding to Spec-Driven Development with Kiro&lt;/a&gt; first appeared on &lt;a href="https://hedrange.com/" rel="noopener noreferrer"&gt;Håkon Eriksen Drange&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>articles</category>
      <category>aws</category>
      <category>kiro</category>
      <category>specdrivendevelopmen</category>
    </item>
    <item>
      <title>Demonstrate practical AWS skills with new microcredentials</title>
      <dc:creator>Håkon Eriksen Drange</dc:creator>
      <pubDate>Fri, 14 Nov 2025 07:58:42 +0000</pubDate>
      <link>https://dev.to/haakoned/demonstrate-practical-aws-skills-with-new-microcredentials-3918</link>
      <guid>https://dev.to/haakoned/demonstrate-practical-aws-skills-with-new-microcredentials-3918</guid>
      <description>&lt;p&gt;AWS has announced a new skills validation program called &lt;a href="https://skillbuilder.aws/category/type/microcredentials" rel="noopener noreferrer"&gt;Microcredentials&lt;/a&gt;. These are more practical and lightweight approaches for validating knowledge than a full, comprehensive certification exam. It’s rewarding to be able to go beyond theoretical knowledge and prove what you’ve actually learned. AWS Certifications and Microcredentials complement each other; validating both your deep technical knowledge and hands-on skills.&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%2F8i5455v8l0o65wcefo3o.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%2F8i5455v8l0o65wcefo3o.png" alt="Image demonstrating AWS microcredential lab overview" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Image credit: AWS – &lt;a href="https://www.aboutamazon.com/news/aws/aws-ai-certification-learning-tools-skills-development" rel="noopener noreferrer"&gt;https://www.aboutamazon.com/news/aws/aws-ai-certification-learning-tools-skills-development&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  This is how it works
&lt;/h2&gt;

&lt;p&gt;The exam labs are in a live AWS-provisioned environment, similar to regular lab/SimuLearn tasks on AWS Skill Builder. During the course of 90 minutes candidates are presented with a set of challenges to be achieved by practical implementation in the AWS Console (ClickOps, no IaC). You will have to diagnose issues and implement solutions on your own, there are no hints or guidance provided. The exam lab cannot be paused or restarted, so if you have to quit you need to start over again. Not much different than an actual certification exam. Candidates failing to meet the passing score objective can go for a re-take after 25 days.&lt;/p&gt;

&lt;p&gt;As of November 2025 the available training options are:&lt;/p&gt;

&lt;h3&gt;
  
  
  Microcredential Preview Experience
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;This microcredential validates your ability to configure and connect basic AWS services in hands-on scenarios. Key focus areas include S3 static hosting, API Gateway connections, Lambda integrations, and DynamoDB storage. This is not a real microcredential exam lab. This is a trial version you can use to familiarize yourself with the interface.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I recommend you start here to become familiar with the concept to set yourself up for success for the actual lab exam.&lt;/p&gt;

&lt;p&gt;AWS Skill Builder link: &lt;a href="https://skillbuilder.aws/learn/JBTFY8M6S8/microcredential-preview-experience/YJFR7KHKR3" rel="noopener noreferrer"&gt;https://skillbuilder.aws/learn/JBTFY8M6S8/microcredential-preview-experience/YJFR7KHKR3&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  AWS Agentic AI Demonstrated
&lt;/h3&gt;

&lt;p&gt;_ &lt;strong&gt;AWS Agentic AI Demonstrated&lt;/strong&gt; is a hands-on exam lab designed to help you validated your AWS skills in the &lt;strong&gt;Agentic AI&lt;/strong&gt; domain_.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Objectives&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Troubleshoot and repair supervisor and specialist Bedrock Agents&lt;/li&gt;
&lt;li&gt;Troubleshoot and repair an Amazon Bedrock knowledge base&lt;/li&gt;
&lt;li&gt;Integrate and fix a Bedrock Agent&lt;/li&gt;
&lt;li&gt;Enhance Bedrock Agent capabilities&lt;/li&gt;
&lt;li&gt;Integrate Bedrock Guardrails with a Bedrock Agent&lt;/li&gt;
&lt;li&gt;Connect a web application chat client with a Bedrock Agent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AWS Skill Builder link: &lt;a href="https://skillbuilder.aws/learn/32Y249P272/aws-agentic-ai-demonstrated/TTAJ5WKYTS" rel="noopener noreferrer"&gt;https://skillbuilder.aws/learn/32Y249P272/aws-agentic-ai-demonstrated/TTAJ5WKYTS&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  AWS Serverless Demonstrated
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;AWS Serverless Demonstrated&lt;/strong&gt; is a hands-on exam lab designed to help you validate your AWS skills in the &lt;strong&gt;Serverless&lt;/strong&gt; domain.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Objectives&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Configure an AWS Lambda function&lt;/li&gt;
&lt;li&gt;Deploy a REST API&lt;/li&gt;
&lt;li&gt;Configure a Step Functions state machine&lt;/li&gt;
&lt;li&gt;Design and implement an event-driven system&lt;/li&gt;
&lt;li&gt;Optimize AWS Lambda functions for various scenarios&lt;/li&gt;
&lt;li&gt;Configure a CI/CD pipeline for serverless applications&lt;/li&gt;
&lt;li&gt;Configure and analyze monitoring and telemetry&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AWS Skill Builder Link: &lt;a href="https://skillbuilder.aws/learn/XV3B4RGA8Q/aws-serverless-demonstrated/BYD5SH8R5C" rel="noopener noreferrer"&gt;https://skillbuilder.aws/learn/XV3B4RGA8Q/aws-serverless-demonstrated/BYD5SH8R5C&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Tip: Completing the &lt;a href="https://skillbuilder.aws/learning-plan/VD4SU58H3W/serverless--knowledge-badge-readiness-path-includes-labs/W62PSCYRZF" rel="noopener noreferrer"&gt;Serverless Knowledge Badge Readiness Path course&lt;/a&gt; first can be helpful (you will also get a badge upon completing a multiple-choice assessment).&lt;/p&gt;

&lt;h2&gt;
  
  
  Result
&lt;/h2&gt;

&lt;p&gt;Passing the microcredential lab exams will unlock some nice new Credly badges you can share with your manager, colleagues and on social media to prove the skills you’ve acquired.&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%2F6nnpsbilllpcbldktb5j.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%2F6nnpsbilllpcbldktb5j.png" width="800" height="241"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My reference: &lt;a href="https://www.credly.com/badges/bc214808-641d-4fb2-b196-e78b530af563" rel="noopener noreferrer"&gt;https://www.credly.com/badges/bc214808-641d-4fb2-b196-e78b530af563&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%2Fr2r66tgi0ox5kvjssi7v.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%2Fr2r66tgi0ox5kvjssi7v.png" width="799" height="263"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My reference: &lt;a href="https://www.credly.com/badges/80b6282b-026e-482e-bc14-9aa801536435" rel="noopener noreferrer"&gt;https://www.credly.com/badges/80b6282b-026e-482e-bc14-9aa801536435&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Good luck!&lt;/p&gt;

&lt;p&gt;The post &lt;a href="https://hedrange.com/2025/11/14/demonstrate-practical-aws-skills-with-new-microcredentials/" rel="noopener noreferrer"&gt;Demonstrate practical AWS skills with new microcredentials&lt;/a&gt; first appeared on &lt;a href="https://hedrange.com/" rel="noopener noreferrer"&gt;Håkon Eriksen Drange&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>articles</category>
      <category>aws</category>
      <category>microcredentials</category>
    </item>
    <item>
      <title>How to use Kiro for AI assisted spec-driven development</title>
      <dc:creator>Håkon Eriksen Drange</dc:creator>
      <pubDate>Mon, 11 Aug 2025 13:35:31 +0000</pubDate>
      <link>https://dev.to/haakoned/how-to-use-kiro-for-ai-assisted-spec-driven-development-2mpa</link>
      <guid>https://dev.to/haakoned/how-to-use-kiro-for-ai-assisted-spec-driven-development-2mpa</guid>
      <description>&lt;p&gt;Read on to learn how to use Kiro for AI assisted spec-driven development of a serverless weather forecasting app, using Terraform for deployment to AWS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Table of contents&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;Kiro introduces the AI assisted spec-driven development workflow&lt;/li&gt;
&lt;li&gt;
Kiro core concepts

&lt;ul&gt;
&lt;li&gt;Core capabilities&lt;/li&gt;
&lt;li&gt;Specs: Plan and build features using structured specifications&lt;/li&gt;
&lt;li&gt;Hooks: Automate repetitive tasks with intelligent triggers&lt;/li&gt;
&lt;li&gt;Agentic chat: Build features through natural conversation with AI&lt;/li&gt;
&lt;li&gt;Steering: Guide AI with custom rules and context&lt;/li&gt;
&lt;li&gt;MCP Servers: Connect external tools and data sources&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Getting Kiro&lt;/li&gt;

&lt;li&gt;

Starting your first Kiro project

&lt;ul&gt;
&lt;li&gt;Vibe&lt;/li&gt;
&lt;li&gt;Spec&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

Setting up Kiro

&lt;ul&gt;
&lt;li&gt;Model Context Protocol (MCP) servers&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Setting up agent steering context&lt;/li&gt;

&lt;li&gt;

Writing your product spec

&lt;ul&gt;
&lt;li&gt;Step 1: Define requirements&lt;/li&gt;
&lt;li&gt;Step 2: Define design&lt;/li&gt;
&lt;li&gt;Step 3: Implement&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

Deploying the final solution

&lt;ul&gt;
&lt;li&gt;Workflow for adding a new feature&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

Learnings and key takeaways

&lt;ul&gt;
&lt;li&gt;Reflections on context&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Kiro pricing&lt;/li&gt;

&lt;li&gt;Conclusion&lt;/li&gt;

&lt;li&gt;Resources&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Since the advent of Generative AI, coding assistants and their evolution has been a topic of much discussion. As the Large Language Models became increasingly more precise, the AI-based coding assistants or companions have been able to produce increasingly more relevant suggestions, which has been incorporated into extensions and new IDEs such as Cursor in addition to the CLI. We’ve evolved from simple suggestions of a few lines to transformation of complete codebases. The focus is pivoting from &lt;em&gt;assistance&lt;/em&gt; to &lt;em&gt;resolution&lt;/em&gt; of a particular problem and &lt;em&gt;outcomes&lt;/em&gt;, what we instruct the software to achieve. &lt;a href="https://en.wikipedia.org/wiki/Vibe_coding" rel="noopener noreferrer"&gt;Vibe Coding&lt;/a&gt; will probably go down in history as one of the main terms of 2025. It can be efficient for prototypes and proof-of-concepts, but how can we know what assumptions and decisions the agent made to get to that result?&lt;/p&gt;

&lt;p&gt;Traditional software development processes are based on initial specification. We need to know the purpose and functionality of what to build before we starting building.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/context-project-rules.html" rel="noopener noreferrer"&gt;Project Rules&lt;/a&gt; and &lt;a href="https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/customizations.html" rel="noopener noreferrer"&gt;Customizations&lt;/a&gt; in &lt;a href="https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/what-is.html" rel="noopener noreferrer"&gt;Amazon Q Developer&lt;/a&gt; were a step in the right direction, but some key challenges I experienced with AI coding assistants earlier in 2025:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Don’t remember context or state. If you shut down your laptop and continue tomorrow the context may be lost (was improved with Q Developer)&lt;/li&gt;
&lt;li&gt;How to share context across multiple developers in a team&lt;/li&gt;
&lt;li&gt;How to get more valuable output according to personal/company preference&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://kiro.dev/blog/understanding-kiro-pricing-specs-vibes-usage-tracking/" rel="noopener noreferrer"&gt;Research referenced by AWS&lt;/a&gt; shows that addressing issues during the development phase is &lt;a href="https://www.cs.cmu.edu/afs/cs/academic/class/17654-f01/www/refs/BB.pdf" rel="noopener noreferrer"&gt;5&lt;/a&gt; to &lt;a href="https://www.researchgate.net/figure/BM-System-Science-Institute-Relative-Cost-of-Fixing-Defects_fig1_255965523" rel="noopener noreferrer"&gt;7&lt;/a&gt; times more costly than resolving them during the planning phase of the software development lifecycle. Similarly, it’s less complex and costly to change a system before going to production.&lt;/p&gt;

&lt;p&gt;This principle holds true even with AI coding assistants. When you take the time to discuss requirements and design with Kiro during the planning phase, a single specification request will often accomplish what would otherwise require multiple vibe iterations during implementation. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Garbage_in,_garbage_out" rel="noopener noreferrer"&gt;Garbage in, garbage out&lt;/a&gt;, you know.&lt;/p&gt;

&lt;p&gt;Luckily, Kiro can now help incorporate that well-known structure into AI assistant coding, in a consistent manner.&lt;/p&gt;

&lt;h2&gt;
  
  
  Kiro introduces the AI assisted spec-driven development workflow
&lt;/h2&gt;

&lt;p&gt;Kiro is a new software development IDE based on Visual Studio Code that turns prompts into clear requirements, structured designs and implementation tasks.&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%2Ft9lfb3fa7dkrux3xhi80.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%2Ft9lfb3fa7dkrux3xhi80.png" width="799" height="206"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The whole process is validated by tests. Code is generated by revolutionary AI agents utilizing the latest and most up-to-date Large Language Models (&lt;a href="https://en.wikipedia.org/wiki/Large_language_model" rel="noopener noreferrer"&gt;LLM&lt;/a&gt;s).&lt;/p&gt;

&lt;p&gt;Kiro leverages &lt;a href="https://www.anthropic.com/claude/sonnet" rel="noopener noreferrer"&gt;Anthropic’s Claude Sonnet 4&lt;/a&gt; under the hood, with the option to fall back to 3.7 (prefer the newest one). These models are specialized in agentic coding and tasks across the entire software development lifecycle from initial planning, implementation, bug fixing, maintenance and refactoring.&lt;/p&gt;

&lt;p&gt;I recommend reading &lt;a href="https://kiro.dev/blog/introducing-kiro/" rel="noopener noreferrer"&gt;Introducing Kiro&lt;/a&gt; and &lt;a href="https://kiro.dev/blog/kiro-and-the-future-of-software-development/" rel="noopener noreferrer"&gt;Kiro and the future of AI spec-driven software development&lt;/a&gt; to get up to speed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Kiro core concepts
&lt;/h2&gt;

&lt;p&gt;Kiro introduces two main modes: Vibe and Spec.&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%2Flgi2ge5p7h5tctlwe2vn.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%2Flgi2ge5p7h5tctlwe2vn.png" width="800" height="451"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Image courtesy of Kiro&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Core capabilities
&lt;/h3&gt;

&lt;p&gt;You can read more about my experiences with these capabilities in the next chapter. Let me introduce the concepts first.&lt;/p&gt;
&lt;h4&gt;
  
  
  Specs: Plan and build features using structured specifications
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;Specs or specifications are structured artifacts that formalize the development process for complex features in your application. They provide a systematic approach to transform high-level ideas into detailed implementation plans with clear tracking and accountability.&lt;/p&gt;

&lt;p&gt;With Kiro’s specs, you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Break down requirements&lt;/strong&gt;  into user stories with acceptance criteria&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build design docs&lt;/strong&gt;  with sequence diagrams and architecture plans&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Track implementation progress&lt;/strong&gt;  across discrete tasks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Collaborate effectively&lt;/strong&gt;  between product and engineering teams&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Reference: &lt;a href="https://kiro.dev/docs/specs/" rel="noopener noreferrer"&gt;https://kiro.dev/docs/specs/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;
  
  
  Hooks: Automate repetitive tasks with intelligent triggers
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;Agent Hooks are automated triggers that execute predefined agent actions when specific events occur in the Kiro IDE. When files are created, saved or deleted you can configure hooks to be run for common tasks to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Maintain consistent code quality&lt;/li&gt;
&lt;li&gt;Prevent security vulnerabilities&lt;/li&gt;
&lt;li&gt;Reduce manual overhead&lt;/li&gt;
&lt;li&gt;Standardize team processes&lt;/li&gt;
&lt;li&gt;Create faster development cycles&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Reference: &lt;a href="https://kiro.dev/docs/hooks/" rel="noopener noreferrer"&gt;https://kiro.dev/docs/hooks/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is an area I have not explored in detail yet.&lt;/p&gt;
&lt;h4&gt;
  
  
  Agentic chat: Build features through natural conversation with AI
&lt;/h4&gt;

&lt;p&gt;Kiro offers a &lt;a href="https://kiro.dev/docs/chat/" rel="noopener noreferrer"&gt;chat&lt;/a&gt; panel where you can interact with your code through natural language conversations. Just tell Kiro what you need. Ask questions about your codebase, request explanations for complex logic, generate new features, debug tricky issues, and automate repetitive tasks—all while Kiro maintains complete context of your project.&lt;/p&gt;
&lt;h4&gt;
  
  
  Steering: Guide AI with custom rules and context
&lt;/h4&gt;

&lt;p&gt;I believe this is one of the most powerful capabilities Kiro introduces. To quote the &lt;a href="https://kiro.dev/docs/" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Steering gives Kiro persistent knowledge about your project through markdown files in &lt;code&gt;.kiro/steering/&lt;/code&gt;. Instead of explaining your conventions in every chat, steering files ensure Kiro consistently follows your established patterns, libraries, and standards.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Consistent Code Generation&lt;/strong&gt;  – Every component, API endpoint, or test follows your team’s established patterns and conventions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reduced Repetition&lt;/strong&gt;  – No need to explain project standards in each conversation. Kiro remembers your preferences.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Team Alignment&lt;/strong&gt;  – All developers work with the same standards, whether they’re new to the project or seasoned contributors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalable Project Knowledge&lt;/strong&gt;  – Documentation that grows with your codebase, capturing decisions and patterns as your project evolves.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Reference: &lt;a href="https://kiro.dev/docs/steering/" rel="noopener noreferrer"&gt;https://kiro.dev/docs/steering/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;
  
  
  MCP Servers: Connect external tools and data sources
&lt;/h4&gt;

&lt;p&gt;In my opinion the main inputs for valuable and tailored results is your combination of Steering and Model Context Protocol servers. MCP extends Kiro’s capabilities by connecting to specialized servers that provide additional tools and context, tailored to your environment.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;MCP is a protocol that allows Kiro to communicate with external servers to access specialized tools and information. For example, the AWS Documentation MCP server provides tools to search, read, and get recommendations from AWS documentation directly within Kiro.&lt;/p&gt;

&lt;p&gt;With MCP, you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Access specialized knowledge bases and documentation&lt;/li&gt;
&lt;li&gt;Integrate with external services and APIs&lt;/li&gt;
&lt;li&gt;Extend Kiro’s capabilities with domain-specific tools&lt;/li&gt;
&lt;li&gt;Create custom tools for your specific workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Reference: &lt;a href="https://kiro.dev/docs/mcp/" rel="noopener noreferrer"&gt;https://kiro.dev/docs/mcp/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Getting Kiro
&lt;/h2&gt;

&lt;p&gt;As of early August 2025 Kiro is still in public preview with limited availability and a waiting list.&lt;/p&gt;

&lt;p&gt;Assuming you have been able to get Kiro from &lt;a href="https://kiro.dev/" rel="noopener noreferrer"&gt;https://kiro.dev/&lt;/a&gt; go through their &lt;a href="https://kiro.dev/docs/getting-started/" rel="noopener noreferrer"&gt;Get started guide&lt;/a&gt; to learn more about the basic concepts.&lt;/p&gt;
&lt;h2&gt;
  
  
  Starting your first Kiro project
&lt;/h2&gt;

&lt;p&gt;Open a new folder to start a new project and you are presented with the option build Vibe or Spec style.&lt;/p&gt;
&lt;h4&gt;
  
  
  Vibe
&lt;/h4&gt;

&lt;p&gt;Chat first, then build. Explore ideas and iterate as you discover needs.&lt;/p&gt;

&lt;p&gt;Great for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rapid exploration and testing&lt;/li&gt;
&lt;li&gt;Building when requirements are unclear&lt;/li&gt;
&lt;li&gt;Implementing a task&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Spec
&lt;/h4&gt;

&lt;p&gt;Plan first, then build. Create requirements and design before coding starts.&lt;/p&gt;

&lt;p&gt;Great for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Thinking through feature in-dept&lt;/li&gt;
&lt;li&gt;Projects needing upfront planning&lt;/li&gt;
&lt;li&gt;Building features in a structured way&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%2F7tpghs4yhl3rw1dw77ia.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%2F7tpghs4yhl3rw1dw77ia.png" width="800" height="624"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In my case I’ve used Amazon Q Developer in VS Code and CLI quite a lot for coding acceleration, so I went directly for Spec. Let’s get back to working with specifications after we have configured the remaining parts of our Kiro workspace.&lt;/p&gt;
&lt;h2&gt;
  
  
  Setting up Kiro
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Model Context Protocol (MCP) servers
&lt;/h3&gt;

&lt;p&gt;Enriching your environment with relevant MCP servers can be a massive boost. Take a look at the official MCP Servers from AWS at &lt;a href="https://github.com/awslabs/mcp" rel="noopener noreferrer"&gt;https://github.com/awslabs/mcp&lt;/a&gt; , there is already a ton available.&lt;/p&gt;

&lt;p&gt;The ones I currently enjoy in Kiro and Amazon Q Developer focusing on Terraform development and AWS are:&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%2Flqyyfkpdftuqjcc7koc4.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%2Flqyyfkpdftuqjcc7koc4.png" width="348" height="241"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/awslabs/mcp/blob/main/src/core-mcp-server" rel="noopener noreferrer"&gt;AWS Core&lt;/a&gt; provides tools for prompt understanding and translation to AWS services&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/awslabs/mcp/blob/main/src/aws-documentation-mcp-server" rel="noopener noreferrer"&gt;AWS Docs&lt;/a&gt; and AWS Knowledge can read, search and recommend from the official, up-to-date AWS Documentation. &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/awslabs/mcp/blob/main/src/aws-api-mcp-server" rel="noopener noreferrer"&gt;AWS API&lt;/a&gt; can suggest for you and call AWS CLI commands on your behalf.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/awslabs/mcp/blob/main/src/cdk-mcp-server" rel="noopener noreferrer"&gt;AWS CDK&lt;/a&gt; can provide guidance and generate CDK stacks.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/awslabs/mcp/blob/main/src/terraform-mcp-server" rel="noopener noreferrer"&gt;AWS Terraform&lt;/a&gt; can search AWS and AWSCC provider docs, Execute Terraform and Terragrunt Commands, run Checkov scans and search user provided Terraform registry modules. Super valuable!&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/awslabs/mcp/blob/main/src/aws-serverless-mcp-server" rel="noopener noreferrer"&gt;AWS Serverless&lt;/a&gt; can provide guidance, search schemas, deploy serverless applications, get metrics and so on.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/awslabs/mcp/blob/main/src/aws-diagram-mcp-server" rel="noopener noreferrer"&gt;AWS Diagram&lt;/a&gt; get generate diagrams, get diagram examples and list icons. You can generate architecture diagrams with official AWS icons, flow and sequence charts and so on. Content can be provided as a static image or in Draw.IO XML format, so that you can finishing up the final touches and corrections yourself.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  - &lt;a href="https://github.com/awslabs/mcp/blob/main/src/aws-pricing-mcp-server" rel="noopener noreferrer"&gt;AWS Pricing&lt;/a&gt; can analyze CDK and Terraform projects, query the official pricing API and generate cost reports, much more efficient than manually working with the AWS Cost Calculator.
&lt;/h2&gt;

&lt;p&gt;The MCP configuration feature in Kiro supports two modes: User Config (global) and Workspace Config.&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%2Fopyg6t449p54el7i1rby.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%2Fopyg6t449p54el7i1rby.png" width="255" height="111"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;| &lt;strong&gt;Feature&lt;/strong&gt; | &lt;strong&gt;User Config&lt;/strong&gt; | &lt;strong&gt;Workspace Config&lt;/strong&gt; |&lt;br&gt;
| Config file location | ~/.kiro/settings.mcp.json | my-kiro-project/.kiro/settings.json |&lt;br&gt;
| Application | Global, across all your Kiro projects | Local, in the current Kiro project |&lt;br&gt;
| Usage guidance | Keep common Kiro context configuration on your global system. | Keep all Kiro project context configuration within the project. |&lt;br&gt;
| | | |&lt;/p&gt;

&lt;p&gt;Personally I prefer Workspace Config and keeping all context in the project. This makes it easier and predictable for other colleagues having the same MCP server configuration. It also yields more consistent outputs. Think about it, if not all team members have the same context settings, the results are not guaranteed to be consistent and could lead to implementation differences and bugs.&lt;/p&gt;

&lt;p&gt;Here is my current Workspace MCP Config, which I have added to a common agent-steering-bootstrap Git repo:&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;textarea tabindex="-1" aria-hidden="true" readonly&amp;gt;{
  "mcpServers": {
    "fetch": {
      "command": "uvx",
      "args": [
        "mcp-server-fetch"
      ],
      "env": {},
      "disabled": false,
      "autoApprove": []
    },
    "aws-docs": {
      "command": "uvx",
      "args": [
        "awslabs.aws-documentation-mcp-server@latest"
      ],
      "env": {
        "FASTMCP_LOG_LEVEL": "ERROR"
      },
      "disabled": false,
      "autoApprove": [
        "search_documentation",
        "read_documentation"
      ]
    },
    "aws-core": {
      "command": "uvx",
      "args": [
        "awslabs.core-mcp-server@latest"
      ],
      "env": {
        "FASTMCP_LOG_LEVEL": "ERROR"
      },
      "disabled": false,
      "autoApprove": []
    },
    "aws-api": {
      "command": "uvx",
      "args": [
        "awslabs.aws-api-mcp-server@latest"
      ],
      "env": {
        "FASTMCP_LOG_LEVEL": "ERROR"
      },
      "disabled": false,
      "autoApprove": []
    },
    "aws-knowledge-mcp-server": {
      "command": "uvx",
      "args": [
        "mcp-proxy",
        "--transport",
        "streamablehttp",
        "https://knowledge-mcp.global.api.aws"
      ],
      "disabled": false,
      "autoApprove": []
    },
    "aws-cdk": {
      "command": "uvx",
      "args": [
        "awslabs.cdk-mcp-server@latest"
      ],
      "env": {
        "FASTMCP_LOG_LEVEL": "ERROR"
      },
      "disabled": false,
      "autoApprove": []
    },
    "aws-terraform": {
      "command": "uvx",
      "args": [
        "awslabs.terraform-mcp-server@latest"
      ],
      "env": {
        "FASTMCP_LOG_LEVEL": "ERROR"
      },
      "disabled": false,
      "autoApprove": []
    },
    "aws-serverless": {
      "command": "uvx",
      "args": [
        "awslabs.aws-serverless-mcp-server@latest"
      ],
      "env": {
        "FASTMCP_LOG_LEVEL": "ERROR"
      },
      "disabled": false,
      "autoApprove": []
    },
    "awslabs-diagram": {
      "command": "uvx",
      "args": [
        "awslabs.aws-diagram-mcp-server"
      ],
      "env": {
        "FASTMCP_LOG_LEVEL": "ERROR"
      },
      "disabled": false,
      "autoApprove": [
        "get_diagram_examples",
        "generate_diagram"
      ]
    },
    "awslabs-pricing": {
      "command": "uvx",
      "args": [
        "awslabs.aws-pricing-mcp-server@latest"
      ],
      "env": {
        "FASTMCP_LOG_LEVEL": "ERROR",
        "AWS_PROFILE": "default",
        "AWS_REGION": "eu-west-1"
      },
      "disabled": false,
      "autoApprove": [
        "get_pricing_service_codes",
        "get_pricing_service_attributes",
        "get_pricing_attribute_values",
        "get_pricing"
      ]
    }
  }
}&amp;lt;/textarea&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "mcpServers": {
    "fetch": {
      "command": "uvx",
      "args": [
        "mcp-server-fetch"
      ],
      "env": {},
      "disabled": false,
      "autoApprove": []
    },
    "aws-docs": {
      "command": "uvx",
      "args": [
        "awslabs.aws-documentation-mcp-server@latest"
      ],
      "env": {
        "FASTMCP_LOG_LEVEL": "ERROR"
      },
      "disabled": false,
      "autoApprove": [
        "search_documentation",
        "read_documentation"
      ]
    },
    "aws-core": {
      "command": "uvx",
      "args": [
        "awslabs.core-mcp-server@latest"
      ],
      "env": {
        "FASTMCP_LOG_LEVEL": "ERROR"
      },
      "disabled": false,
      "autoApprove": []
    },
    "aws-api": {
      "command": "uvx",
      "args": [
        "awslabs.aws-api-mcp-server@latest"
      ],
      "env": {
        "FASTMCP_LOG_LEVEL": "ERROR"
      },
      "disabled": false,
      "autoApprove": []
    },
    "aws-knowledge-mcp-server": {
      "command": "uvx",
      "args": [
        "mcp-proxy",
        "--transport",
        "streamablehttp",
        "https://knowledge-mcp.global.api.aws"
      ],
      "disabled": false,
      "autoApprove": []
    },
    "aws-cdk": {
      "command": "uvx",
      "args": [
        "awslabs.cdk-mcp-server@latest"
      ],
      "env": {
        "FASTMCP_LOG_LEVEL": "ERROR"
      },
      "disabled": false,
      "autoApprove": []
    },
    "aws-terraform": {
      "command": "uvx",
      "args": [
        "awslabs.terraform-mcp-server@latest"
      ],
      "env": {
        "FASTMCP_LOG_LEVEL": "ERROR"
      },
      "disabled": false,
      "autoApprove": []
    },
    "aws-serverless": {
      "command": "uvx",
      "args": [
        "awslabs.aws-serverless-mcp-server@latest"
      ],
      "env": {
        "FASTMCP_LOG_LEVEL": "ERROR"
      },
      "disabled": false,
      "autoApprove": []
    },
    "awslabs-diagram": {
      "command": "uvx",
      "args": [
        "awslabs.aws-diagram-mcp-server"
      ],
      "env": {
        "FASTMCP_LOG_LEVEL": "ERROR"
      },
      "disabled": false,
      "autoApprove": [
        "get_diagram_examples",
        "generate_diagram"
      ]
    },
    "awslabs-pricing": {
      "command": "uvx",
      "args": [
        "awslabs.aws-pricing-mcp-server@latest"
      ],
      "env": {
        "FASTMCP_LOG_LEVEL": "ERROR",
        "AWS_PROFILE": "default",
        "AWS_REGION": "eu-west-1"
      },
      "disabled": false,
      "autoApprove": [
        "get_pricing_service_codes",
        "get_pricing_service_attributes",
        "get_pricing_attribute_values",
        "get_pricing"
      ]
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setting up agent steering context
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Steering gives Kiro persistent knowledge about your project through markdown files in &lt;code&gt;.kiro/steering/&lt;/code&gt;. Instead of explaining your conventions in every chat, steering files ensure Kiro consistently follows your established patterns, libraries, and standards.&lt;/p&gt;

&lt;p&gt;Key benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Consistent Code Generation&lt;/strong&gt;  – Every component, API endpoint, or test follows your team’s established patterns and conventions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reduced Repetition&lt;/strong&gt;  – No need to explain project standards in each conversation. Kiro remembers your preferences.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Team Alignment&lt;/strong&gt;  – All developers work with the same standards, whether they’re new to the project or seasoned contributors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalable Project Knowledge&lt;/strong&gt;  – Documentation that grows with your codebase, capturing decisions and patterns as your project evolves.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Reference: &lt;a href="https://kiro.dev/docs/steering/" rel="noopener noreferrer"&gt;https://kiro.dev/docs/steering/&lt;/a&gt;&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%2Fcs0n0uyoqiveqgudy5fx.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%2Fcs0n0uyoqiveqgudy5fx.png" width="338" height="129"&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%2Flqc2yx07n6afcab7hp2z.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%2Flqc2yx07n6afcab7hp2z.png" width="377" height="145"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;MCP agent steering contains more background information and guidance about how Kiro can leverage the active MCP servers.&lt;/p&gt;

&lt;p&gt;Product focuses on common product development context and principles.&lt;/p&gt;

&lt;p&gt;Structure focuses on how the files in your codebase are structured, to align with company standards and preferences.&lt;/p&gt;

&lt;p&gt;In Tech I define general patterns and principles for approaching solutions deployed to AWS with Terraform.&lt;/p&gt;

&lt;p&gt;Most companies have product development principles, architecture and software guidelines documented in their internal wikis. This context is crucial to get into Kiro Agent Steering context, to get outputs matching with company and team preferences.&lt;/p&gt;

&lt;p&gt;Example from tech.md:&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;textarea tabindex="-1" aria-hidden="true" readonly&amp;gt;# Technology Stack

This document outlines the technical foundation and tooling for the project.

## Build System &amp;amp;amp; Tools
- CI/CD is out of scope of this Terraform module.

## Application Tech Stack
- Python3, Boto3, Jinja templating etc.
- Unit testing of core functionality
- Basic testing of Terraform code
- Provide /health endpoint for REST APIs
- Python operations should take place in a virtual environment where optimal Python version is installed with pyenv

## Infrastructure Tech Stack
- Terraform for Infrastructure-as-Code
- Terraform providers aws and awscc, if necessary
- Leverage community modules from https://github.com/terraform-aws-modules as relevant
- AWS Serverless architecture options are preferred for minimal operational overhead
- The AWS infrastructure is Well-Architected
- The AWS infrastructure is secure as per the latest CIS AWS Security Hub control standard
- Terraform code is unit tested Terraform's native testing framework, HCL-based tests.

## Observability
- For serverless components, AWS X-Ray is leveraged for tracing
- Logs are directed to AWS CloudWatch Logs. CloudWatch Logs groups have a retention period of 180 days. 
- A solution specific AWS Cloudwatch Dashboard which includes relevant CloudWatch metrics for reliability, performance and cost, in addition to a list over the last failing requests

### Pre-commit for Terraform
- Pre-commit is installed and leveraged for validation and formatting. 
  - terraform_fmt
  - terraform_docs in main README.md
  - check-merge-conflict
  - trailing-whitespace
  - mixed-line-ending

Example .pre-commit-config.yaml located in the root directory:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;repos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;repo: &lt;a href="https://github.com/antonbabenko/pre-commit-terraform" rel="noopener noreferrer"&gt;https://github.com/antonbabenko/pre-commit-terraform&lt;/a&gt;
rev: v1.77.3
hooks:

&lt;ul&gt;
&lt;li&gt;id: terraform_fmt&lt;/li&gt;
&lt;li&gt;id: terraform_docs
args: ["--args=--sort-by required"]&lt;/li&gt;
&lt;li&gt;id: terraform_checkov
args:

&lt;ul&gt;
&lt;li&gt;--args=--quiet&lt;/li&gt;
&lt;li&gt;--args=--download-external-modules false&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;li&gt;repo: &lt;a href="https://github.com/pre-commit/pre-commit-hooks" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;a href="https://github.com/pre-commit/pre-commit-hooks" rel="noopener noreferrer"&gt;https://github.com/pre-commit/pre-commit-hooks&lt;/a&gt;
rev: v4.4.0
hooks:

&lt;ul&gt;
&lt;li&gt;id: check-merge-conflict&lt;/li&gt;
&lt;li&gt;id: trailing-whitespace
args: [--markdown-linebreak-ext=md]&lt;/li&gt;
&lt;li&gt;id: mixed-line-ending
args: ["--fix=lf"]
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
## Documentation
- AWS Labs Diagram MCP server is used to produce relevant architecture, flow and sequence diagrams, included in main README.md
- AWS Labs Pricing MCP server is used to perform a basic cost calculation of the solution, included in the main README.md
- Every task should also ensure relevant and clear documentation is created or up to date. Prefer simple and user friendly documentation, don't overcomplicate.
- All documentation follows markdown format and is stored in the `docs/` directory
- Architecture diagrams are generated programmatically using the AWS diagram MCP server
- Cost analysis documentation includes detailed breakdowns, usage projections, and optimization recommendations
- Documentation includes deployment guides, troubleshooting guides, and operational runbooks
- There should be an examples folder with README.md explaining how to include the Terraform module call in an existing CI/CD codebase.
- In documentation, provide TL;DR to make it easy and quick for developers to get up to speed. 
- In high level project documentation, include an executive summary for target group project owners, to articulate functionality and the value the solution provides.

## Cost Management
- AWS Labs Pricing MCP server provides accurate cost calculations for the infrastructure components of the solution.
- Cost analysis should include environment-specific projections (staging and production).
- Cost analysis should include AWS region comparison of eu-west-1, eu-central-1 and eu-north-1 for the production environment.
- CloudWatch cost metrics and dashboards provide real-time cost monitoring.
- A solution specific AWS Budget is deployed, based on infrastructure tag Key Service. Budget alerts prevents unexpected charges.
- Guidance is provided for the top three cost items that may increase with heavy production load. 
- Cost documentation is included in the main `README.md`

## Principles
- Favor KISS over complexity, simplicity over comprehensibility
- Respect and adopt well-known cloud based architecture and integration patterns

&amp;lt;/textarea&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Technology Stack

This document outlines the technical foundation and tooling for the project.

## Build System &amp;amp; Tools
- CI/CD is out of scope of this Terraform module.

## Application Tech Stack
- Python3, Boto3, Jinja templating etc.
- Unit testing of core functionality
- Basic testing of Terraform code
- Provide /health endpoint for REST APIs
- Python operations should take place in a virtual environment where optimal Python version is installed with pyenv

## Infrastructure Tech Stack
- Terraform for Infrastructure-as-Code
- Terraform providers aws and awscc, if necessary
- Leverage community modules from https://github.com/terraform-aws-modules as relevant
- AWS Serverless architecture options are preferred for minimal operational overhead
- The AWS infrastructure is Well-Architected
- The AWS infrastructure is secure as per the latest CIS AWS Security Hub control standard
- Terraform code is unit tested Terraform's native testing framework, HCL-based tests.

## Observability
- For serverless components, AWS X-Ray is leveraged for tracing
- Logs are directed to AWS CloudWatch Logs. CloudWatch Logs groups have a retention period of 180 days. 
- A solution specific AWS Cloudwatch Dashboard which includes relevant CloudWatch metrics for reliability, performance and cost, in addition to a list over the last failing requests

### Pre-commit for Terraform
- Pre-commit is installed and leveraged for validation and formatting. 
  - terraform_fmt
  - terraform_docs in main README.md
  - check-merge-conflict
  - trailing-whitespace
  - mixed-line-ending

Example .pre-commit-config.yaml located in the root directory:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;repos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;repo: &lt;a href="https://github.com/antonbabenko/pre-commit-terraform" rel="noopener noreferrer"&gt;https://github.com/antonbabenko/pre-commit-terraform&lt;/a&gt;
rev: v1.77.3
hooks:

&lt;ul&gt;
&lt;li&gt;id: terraform_fmt&lt;/li&gt;
&lt;li&gt;id: terraform_docs
args: ["--args=--sort-by required"]&lt;/li&gt;
&lt;li&gt;id: terraform_checkov
args:

&lt;ul&gt;
&lt;li&gt;--args=--quiet&lt;/li&gt;
&lt;li&gt;--args=--download-external-modules false&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;li&gt;repo: &lt;a href="https://github.com/pre-commit/pre-commit-hooks" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;a href="https://github.com/pre-commit/pre-commit-hooks" rel="noopener noreferrer"&gt;https://github.com/pre-commit/pre-commit-hooks&lt;/a&gt;
rev: v4.4.0
hooks:

&lt;ul&gt;
&lt;li&gt;id: check-merge-conflict&lt;/li&gt;
&lt;li&gt;id: trailing-whitespace
args: [--markdown-linebreak-ext=md]&lt;/li&gt;
&lt;li&gt;id: mixed-line-ending
args: ["--fix=lf"]
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
## Documentation
- AWS Labs Diagram MCP server is used to produce relevant architecture, flow and sequence diagrams, included in main README.md
- AWS Labs Pricing MCP server is used to perform a basic cost calculation of the solution, included in the main README.md
- Every task should also ensure relevant and clear documentation is created or up to date. Prefer simple and user friendly documentation, don't overcomplicate.
- All documentation follows markdown format and is stored in the `docs/` directory
- Architecture diagrams are generated programmatically using the AWS diagram MCP server
- Cost analysis documentation includes detailed breakdowns, usage projections, and optimization recommendations
- Documentation includes deployment guides, troubleshooting guides, and operational runbooks
- There should be an examples folder with README.md explaining how to include the Terraform module call in an existing CI/CD codebase.
- In documentation, provide TL;DR to make it easy and quick for developers to get up to speed. 
- In high level project documentation, include an executive summary for target group project owners, to articulate functionality and the value the solution provides.

## Cost Management
- AWS Labs Pricing MCP server provides accurate cost calculations for the infrastructure components of the solution.
- Cost analysis should include environment-specific projections (staging and production).
- Cost analysis should include AWS region comparison of eu-west-1, eu-central-1 and eu-north-1 for the production environment.
- CloudWatch cost metrics and dashboards provide real-time cost monitoring.
- A solution specific AWS Budget is deployed, based on infrastructure tag Key Service. Budget alerts prevents unexpected charges.
- Guidance is provided for the top three cost items that may increase with heavy production load. 
- Cost documentation is included in the main `README.md`

## Principles
- Favor KISS over complexity, simplicity over comprehensibility
- Respect and adopt well-known cloud based architecture and integration patterns

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Writing your product spec
&lt;/h2&gt;

&lt;p&gt;Product specs or specifications are structured artifacts that formalize the development process. They provide a systematic approach to transform high-level ideas into detailed implementation plans with clear tracking and accountability.&lt;/p&gt;

&lt;p&gt;The workflow is illustrated below:&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%2Fg8sk2x4xwbhvpqyyzhnn.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%2Fg8sk2x4xwbhvpqyyzhnn.png" width="584" height="1024"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From the Kiro pane, click the &lt;code&gt;+&lt;/code&gt; button under  &lt;strong&gt;Specs&lt;/strong&gt;. Alternatively, choose  &lt;strong&gt;Spec&lt;/strong&gt;  from the chat pane.&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%2Fvntspi3diwkberyqo5to.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%2Fvntspi3diwkberyqo5to.png" width="261" height="58"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Describe your project idea.&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%2F6l3tksc2v8eolt0c0dy1.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%2F6l3tksc2v8eolt0c0dy1.png" width="647" height="157"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A requirements Markdown file is created in a folder with the spec name weather-forecast-app.&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%2Fvcyqwep1j4xgxm4t0ebb.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%2Fvcyqwep1j4xgxm4t0ebb.png" width="798" height="141"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 1: Define requirements
&lt;/h4&gt;

&lt;p&gt;The requirements.md file should define user stories with acceptance criterias in &lt;a href="https://alistairmavin.com/ears/" rel="noopener noreferrer"&gt;EARS&lt;/a&gt; notation, similar to common agile development practice. Define what we would like to achieve and which problems we propose to solve. HOW we plan to solve it will come afterwards in the Design phase.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://alistairmavin.com/ears/" rel="noopener noreferrer"&gt;EARS&lt;/a&gt;, which stands for Easy Approach to Requirements Syntax, is a method for writing clear and unambiguous requirements using a structured set of rules and keywords. Alistair Mavin and colleagues at Rolls-Royce PLC developed EARS whilst analysing the airworthiness regulations for a jet engine’s control system. The structured format makes it easy to understand what is expected, reducing misinterpretations. Clearer requirements lead to better test cases and easier verification of application functionality.&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;textarea tabindex="-1" aria-hidden="true" readonly&amp;gt;WHEN [condition/event]
THE SYSTEM SHALL [expected behavior]&amp;lt;/textarea&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WHEN [condition/event]
THE SYSTEM SHALL [expected behavior]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Starting writing your User Stories as acceptance criterias in EARS format. Remember, as described in the Introduction, the more complete context you provide, including what your team usually have in their minds and have learned by experience how you do you things in your company, be as specific as you can. Investing more time in producing well-crafted specifications can reduce time spent on modifications and troubleshooting.&lt;/p&gt;

&lt;p&gt;Remember, common principles and guidelines are defined as Agent Steering Context. Product Spec focuses on the functionality of the application. As the system grows you can create additional specifications and manage requirements in logical separation.&lt;/p&gt;

&lt;p&gt;This is how the &lt;a href="https://github.com/haakond/terraform-aws-weather-forecast/blob/main/.kiro/specs/weather-forecast-app/requirements.md" rel="noopener noreferrer"&gt;.kiro/specs/weather-forecast-app/requirements.md&lt;/a&gt; for the example weather forecast application looks like:&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;textarea tabindex="-1" aria-hidden="true" readonly&amp;gt;# Requirements Document

## Introduction

This specification will create a weather forecast application, to be deployed with Terraform on AWS serverless infrastructure.

### Requirement 1

**User Story:** As an end user, I will access a web site which compares the weather forecast for tomorrow for the European cities Oslo (Norway), Paris (France), London (United Kingdom) and Barcelona (Spain).

#### Acceptance Criteria

1. WHEN an end user is accessing the service THEN the system SHALL display a simple web site with a fancy design for the weather forecast for the cities as described in the User Story
2. WHEN an end user is accessing the service THEN the system SHALL be snappy and respond fast
3. WHEN an end user is accessing the service on a mobile device THEN the design SHALL be optimized for display on a small screen
4. WHEN static content is served to end users THEN the system SHALL set Cache-Control headers with Max-Age of 900 seconds (15 minutes) to optimize performance and reduce server load

### Requirement 2

**User Story:** As a developer, my application requirements are as follows:

#### Acceptance Criteria

1. WHEN the weather-forecast-app is generated THEN the system SHALL provide a modern front-end application
2. WHEN the weather-forecast-app is generated THEN the system SHALL look up weather forecasts from https://api.met.no/weatherapi/locationforecast/2.0/documentation and cache the results for 1 hour
3. WHEN the weather-forecast-app is generated THEN the system SHALL respect the Terms of Service defined at https://developer.yr.no/doc/TermsOfService/
4. WHEN the weather-forecast-app is generated THEN the system SHALL be tested
5. WHEN the Lambda function successfully retrieves weather data from the backend API THEN the system SHALL set cache-control: max-age=60 on the HTTP response
6. WHEN the Lambda function fails to retrieve weather data from the backend API THEN the system SHALL set cache-control: max-age=0 on the HTTP response
7. WHEN the frontend displays weather data THEN the system SHALL show the Last updated timestamp from the lastUpdated field in the API response
8. WHEN weather data is cached in DynamoDB and the weather API does not provide timestamp information THEN the system SHALL use the DynamoDB cache timestamp as the lastUpdated value in the API response

### Requirement 3

**User Story:** As a developer, my cloud infrastructure requirements are as follows:

#### Acceptance Criteria

1. WHEN the infrastructure is generated THEN the codebase SHALL be organized as one, self-contained Terraform module
2. WHEN the infrastructure is generated THEN the system SHALL require basic unit tests and infrastructure-as-code validation to be successful
3. WHEN the infrastructure is deployed THEN the system SHALL create AWS resources with appropriate tags like Service:weather-forecast-app.
4. WHEN the infrastructure is deployed THEN the system SHALL package and deploy the weather-forecast-app code
5. WHEN the infrastructure is deployed THEN the system SHALL provide accessible endpoints for testing
6. WHEN the infrastructure is deployed THEN the system SHALL include required IAM roles and permissions
7. WHEN the infrastructure is deployed THEN the system SHALL output relevant URLs or connection information
8. WHEN the infrastructure is deployed THEN the system SHALL be configured for high availability
9. WHEN the CloudFront distribution is deployed THEN the system SHALL use price class 100 to optimize costs while covering Europe and the United States edge locations
10. WHEN the CloudFront distribution is deployed THEN the system SHALL allow only GET, HEAD, and OPTIONS HTTP methods for security and performance optimization
11. WHEN the CloudFront distribution is deployed THEN the system SHALL configure caching policies based on query parameters to optimize cache efficiency
12. WHEN the CloudFront distribution is deployed THEN the system SHALL set the default TTL to 900 seconds (15 minutes) to align with static content caching requirements&amp;lt;/textarea&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Requirements Document

## Introduction

This specification will create a weather forecast application, to be deployed with Terraform on AWS serverless infrastructure.

### Requirement 1

**User Story:** As an end user, I will access a web site which compares the weather forecast for tomorrow for the European cities Oslo (Norway), Paris (France), London (United Kingdom) and Barcelona (Spain).

#### Acceptance Criteria

1. WHEN an end user is accessing the service THEN the system SHALL display a simple web site with a fancy design for the weather forecast for the cities as described in the User Story
2. WHEN an end user is accessing the service THEN the system SHALL be snappy and respond fast
3. WHEN an end user is accessing the service on a mobile device THEN the design SHALL be optimized for display on a small screen
4. WHEN static content is served to end users THEN the system SHALL set Cache-Control headers with Max-Age of 900 seconds (15 minutes) to optimize performance and reduce server load

### Requirement 2

**User Story:** As a developer, my application requirements are as follows:

#### Acceptance Criteria

1. WHEN the weather-forecast-app is generated THEN the system SHALL provide a modern front-end application
2. WHEN the weather-forecast-app is generated THEN the system SHALL look up weather forecasts from https://api.met.no/weatherapi/locationforecast/2.0/documentation and cache the results for 1 hour
3. WHEN the weather-forecast-app is generated THEN the system SHALL respect the Terms of Service defined at https://developer.yr.no/doc/TermsOfService/
4. WHEN the weather-forecast-app is generated THEN the system SHALL be tested
5. WHEN the Lambda function successfully retrieves weather data from the backend API THEN the system SHALL set cache-control: max-age=60 on the HTTP response
6. WHEN the Lambda function fails to retrieve weather data from the backend API THEN the system SHALL set cache-control: max-age=0 on the HTTP response
7. WHEN the frontend displays weather data THEN the system SHALL show the Last updated timestamp from the lastUpdated field in the API response
8. WHEN weather data is cached in DynamoDB and the weather API does not provide timestamp information THEN the system SHALL use the DynamoDB cache timestamp as the lastUpdated value in the API response

### Requirement 3

**User Story:** As a developer, my cloud infrastructure requirements are as follows:

#### Acceptance Criteria

1. WHEN the infrastructure is generated THEN the codebase SHALL be organized as one, self-contained Terraform module
2. WHEN the infrastructure is generated THEN the system SHALL require basic unit tests and infrastructure-as-code validation to be successful
3. WHEN the infrastructure is deployed THEN the system SHALL create AWS resources with appropriate tags like Service:weather-forecast-app.
4. WHEN the infrastructure is deployed THEN the system SHALL package and deploy the weather-forecast-app code
5. WHEN the infrastructure is deployed THEN the system SHALL provide accessible endpoints for testing
6. WHEN the infrastructure is deployed THEN the system SHALL include required IAM roles and permissions
7. WHEN the infrastructure is deployed THEN the system SHALL output relevant URLs or connection information
8. WHEN the infrastructure is deployed THEN the system SHALL be configured for high availability
9. WHEN the CloudFront distribution is deployed THEN the system SHALL use price class 100 to optimize costs while covering Europe and the United States edge locations
10. WHEN the CloudFront distribution is deployed THEN the system SHALL allow only GET, HEAD, and OPTIONS HTTP methods for security and performance optimization
11. WHEN the CloudFront distribution is deployed THEN the system SHALL configure caching policies based on query parameters to optimize cache efficiency
12. WHEN the CloudFront distribution is deployed THEN the system SHALL set the default TTL to 900 seconds (15 minutes) to align with static content caching requirements
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you prefer, you can write your requirements the way you are used to and then click &lt;em&gt;Refine&lt;/em&gt; to have Kiro help you format them in EARS format, but I would say it’s good team practice to align to the EARS format.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 2: Define design
&lt;/h4&gt;

&lt;p&gt;Switch to the design tab and click Refine to generate design specification based on the defined requirements, merged with the Agent Steering context configuration.&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%2Fd7i1zp91rya4350ppurc.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%2Fd7i1zp91rya4350ppurc.png" width="798" height="153"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then Kiro starts working.&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%2Fxumgk0hxkde1udq557f1.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%2Fxumgk0hxkde1udq557f1.png" width="800" height="473"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Kiro has now populated design.md. When I take a closer look I don’t see anything addressing the Weather API Service Terms of Service rule: You must identify yourself (set custom HTTP User-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%2Fshlyxznld20l0sxri1b1.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%2Fshlyxznld20l0sxri1b1.png" width="800" height="169"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can have Kiro help refine and add more requirements as you go along.&lt;/p&gt;

&lt;p&gt;Now the design looks good to me, so let’s move on to generate the implementation plan.&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%2F4b3sl9qma2c7ddnpnr4x.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%2F4b3sl9qma2c7ddnpnr4x.png" width="800" height="461"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;textarea tabindex="-1" aria-hidden="true" readonly&amp;gt;# Weather Forecast App Design Document

## Overview

The weather forecast application is a serverless web application that displays tomorrow's weather forecast for four European cities: Oslo (Norway), Paris (France), London (United Kingdom), and Barcelona (Spain). The application will be deployed on AWS using Terraform infrastructure-as-code and will integrate with the Norwegian Meteorological Institute's weather API.

### Key Design Principles
- **Serverless-first architecture** for minimal operational overhead
- **Mobile-responsive design** for optimal user experience across devices
- **Fast response times** through efficient caching and CDN distribution
- **Well-architected AWS infrastructure** following security and reliability best practices

## Architecture

### High-Level Architecture
The application follows a serverless architecture pattern with the following components:

1. **Frontend** : Static web application hosted on S3 with CloudFront distribution
2. **Backend API** : AWS Lambda functions for weather data processing
3. **Data Layer** : DynamoDB for caching weather data and API rate limiting
4. **External Integration** : Norwegian Meteorological Institute API (api.met.no)

### Architecture Rationale
- **Static hosting with S3/CloudFront** : Provides fast global content delivery and handles traffic spikes efficiently
- **Lambda functions** : Serverless compute eliminates server management and scales automatically
- **DynamoDB** : NoSQL database perfect for caching weather data with TTL capabilities
- **API Gateway** : Provides managed API endpoints with built-in throttling and monitoring

## SNIP END&amp;lt;/textarea&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Weather Forecast App Design Document

## Overview

The weather forecast application is a serverless web application that displays tomorrow's weather forecast for four European cities: Oslo (Norway), Paris (France), London (United Kingdom), and Barcelona (Spain). The application will be deployed on AWS using Terraform infrastructure-as-code and will integrate with the Norwegian Meteorological Institute's weather API.

### Key Design Principles
- **Serverless-first architecture** for minimal operational overhead
- **Mobile-responsive design** for optimal user experience across devices
- **Fast response times** through efficient caching and CDN distribution
- **Well-architected AWS infrastructure** following security and reliability best practices

## Architecture

### High-Level Architecture
The application follows a serverless architecture pattern with the following components:

1. **Frontend** : Static web application hosted on S3 with CloudFront distribution
2. **Backend API** : AWS Lambda functions for weather data processing
3. **Data Layer** : DynamoDB for caching weather data and API rate limiting
4. **External Integration** : Norwegian Meteorological Institute API (api.met.no)

### Architecture Rationale
- **Static hosting with S3/CloudFront** : Provides fast global content delivery and handles traffic spikes efficiently
- **Lambda functions** : Serverless compute eliminates server management and scales automatically
- **DynamoDB** : NoSQL database perfect for caching weather data with TTL capabilities
- **API Gateway** : Provides managed API endpoints with built-in throttling and monitoring

## SNIP END
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The design turned out to be grow into more than 300 lines, so I’m just including a snippet here. You can review the complete file here: &lt;a href="https://github.com/haakond/terraform-aws-weather-forecast/blob/main/.kiro/specs/weather-forecast-app/design.md" rel="noopener noreferrer"&gt;.kiro/specs/weather-forecast-app/design.md&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This looks good to me, let’s proceed to generate the implementation plan.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 3: Implement
&lt;/h4&gt;

&lt;p&gt;Kiro has now generated a task list for implementation based on the previous steps.&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%2Fm7cgjflshii708d1rq3a.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%2Fm7cgjflshii708d1rq3a.png" width="799" height="491"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/haakond/terraform-aws-weather-forecast/blob/main/.kiro/specs/weather-forecast-app/tasks.md" rel="noopener noreferrer"&gt;.kiro/specs/weather-forecast-app/tasks.md&lt;/a&gt; now looks like this:&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;textarea tabindex="-1" aria-hidden="true" readonly&amp;gt;# Implementation Plan

- [] 1. Set up project structure and configuration
  - Create Terraform module directory structure following best practices
  - Set up Python virtual environment with pyenv for application development
  - Configure pre-commit hooks for Terraform validation and formatting
  - Create basic project documentation structure with docs/ directory
  - _Requirements: 3.1, 3.2_

- [] 2. Implement simplified Lambda weather service
  - [] 2.1 Create embedded weather service in Lambda handler
    - Implement weather data fetching directly in lambda_handler.py using urllib
    - Define city configuration with coordinates for Oslo, Paris, London, Barcelona
    - Create weather data processing and transformation logic embedded in handler
    - Implement proper User-Agent header with configurable company website
    - Add rate limiting with simple delays between API calls
    - _Requirements: 2.2, 2.3, 1.1_

  - [] 2.2 Implement weather API integration and processing
    - Create fetch_weather_data function for met.no API calls
    - Implement extract_tomorrow_forecast for parsing API responses
    - Add weather condition mapping and error handling
    - Create process_city_weather for individual city processing
    - Implement get_weather_summary for all cities with delay between calls
    - _Requirements: 2.2, 1.2_

- [] 3. Build Lambda function infrastructure
  - [] 3.1 Create simplified Lambda function handler
    - Implement main Lambda handler with embedded weather service
    - Add environment variable configuration for company website
    - Implement proper error handling and logging with standardized responses
    - Create /health endpoint for monitoring with environment information
    - Add CORS support and OPTIONS request handling
    - _Requirements: 2.1, 2.4, 3.6_

  - [] 3.2 Add DynamoDB caching to simplified Lambda handler
    - Implement DynamoDB caching directly in the lambda_handler.py file
    - Add cache check before API calls and cache storage after successful API responses
    - Implement 1-hour TTL (3600 seconds) for cached weather data
    - Add error handling for DynamoDB operations with fallback to API calls
    - Use boto3 client for DynamoDB operations embedded in the handler
    - _Requirements: 2.2, 1.2, 3.6_

- [] 4. Update Terraform infrastructure for simplified approach
  - [] 4.1 Maintain DynamoDB table configuration for caching
    - Keep existing DynamoDB table from Terraform backend module
    - Maintain DynamoDB-related IAM permissions for Lambda role
    - Ensure DynamoDB table name is passed to Lambda via environment variable
    - Keep TTL configuration for 1-hour cache expiration
    - Maintain existing tests for DynamoDB validation
    - _Requirements: 3.1, 3.6, 3.8_

  - [] 4.2 Update Lambda function Terraform module for simplified deployment
    - Update Terraform configuration for simplified Lambda function
    - Maintain DynamoDB environment variables (table name) and permissions
    - Keep COMPANY_WEBSITE environment variable configuration
    - Maintain X-Ray tracing and CloudWatch logging
    - Keep IAM role with DynamoDB permissions for caching
    - _Requirements: 3.1, 3.4, 3.6, 3.8_

  - [] 4.3 Maintain API Gateway configuration
    - Keep existing API Gateway REST API configuration
    - Maintain CORS settings and rate limiting
    - Keep Lambda integration with proper error handling
    - Maintain CloudWatch logging configuration
    - Keep existing tests for the API Gateway setup
    - _Requirements: 3.1, 3.5, 3.6_

- [] 5. Create frontend application
  - [] 5.1 Build responsive weather display components
    - Create React components for weather card display
    - Implement responsive grid layout for four cities
    - Add loading states and error handling UI
    - Ensure mobile-optimized design with proper breakpoints
    - _Requirements: 1.1, 1.2, 1.3, 2.1_

  - [] 5.2 Implement API integration and state management
    - Create API client for backend weather service
    - Implement data fetching with error handling and retries
    - Add browser-side caching strategy respecting 1-hour backend cache
    - Create loading and error state management
    - _Requirements: 1.2, 2.1, 2.2_

  - [] 5.3 Add weather icons and styling
    - Implement weather condition icon mapping
    - Create CSS styling for responsive design
    - Add animations and transitions for better UX
    - Ensure accessibility compliance (WCAG)
    - _Requirements: 1.1, 1.3, 2.1_

  - [] 5.4 Optimize frontend build for caching
    - Configure build process to generate static assets optimized for 15-minute caching
    - Ensure proper file naming and versioning for cache busting when needed
    - Validate that all static assets (HTML, CSS, JS, images) are properly configured
    - _Requirements: 1.2, 1.4_

- [] 6. Configure static hosting infrastructure
  - [] 6.1 Create S3 bucket for static hosting
    - Implement Terraform module for S3 bucket configuration
    - Configure bucket policies for static website hosting
    - Set up versioning and lifecycle policies
    - Add proper IAM permissions for deployment
    - Write basic tests for the S3 configuration
    - _Requirements: 3.1, 3.4, 3.6_

  - [] 6.2 Set up CloudFront distribution
    - Create Terraform module for CloudFront CDN
    - Configure cache behaviors and TTL settings
    - Set up origin failover for high availability
    - Add security headers and HTTPS redirection
    - Write basic tests for the Cloudfront configuration
    - _Requirements: 1.2, 3.1, 3.8_

  - [] 6.4 Configure CloudFront price class and optimization settings
    - Update CloudFront distribution to use price class 100 (PriceClass_100)
    - Configure allowed HTTP methods to GET, HEAD, and OPTIONS only
    - Set up caching policy configuration based on query parameters
    - Configure default TTL to 900 seconds (15 minutes)
    - Ensure coverage includes Europe and United States edge locations
    - Validate cost optimization while maintaining performance for target regions
    - Update Terraform configuration with appropriate price_class, allowed_methods, and caching parameters
    - Test CloudFront distribution functionality with new configuration
    - _Requirements: 3.9, 3.10, 3.11, 3.12_

  - [] 6.3 Configure Cache-Control headers for static content
    - Configure S3 bucket metadata to set Cache-Control: max-age=900 for all static assets
    - Update CloudFront cache behaviors to respect and forward Cache-Control headers
    - Ensure consistent 15-minute caching for HTML, CSS, JavaScript, and image files
    - _Requirements: 1.2, 1.4_

- [] 7. Implement monitoring and observability
  - [] 7.1 Create simple and intuitive CloudWatch dashboard and alarms
    - Implement Terraform module for CloudWatch dashboard
    - Configure the most important alarms for Lambda errors, API Gateway 5xx, and DynamoDB throttling
    - Set up custom metrics for weather API success rates
    - Add log retention policies (180 days)
    - _Requirements: 3.6, 3.7_

  - [] 7.2 Set up AWS Budget and cost monitoring
    - Create Terraform module for AWS Budget with Service tag filter
    - Configure budget alerts for cost thresholds
    - Implement simple and intuitive cost monitoring Cloudwatch dashboard
    - _Requirements: 3.3, 3.7_

- [] 8. Create deployment and testing automation
  - [] 8.1 Implement Terraform module packaging
    - Create main Terraform module with all sub-modules
    - Configure variable definitions and outputs
    - Add module documentation with terraform-docs
    - Create examples/ directory with usage examples
    - _Requirements: 3.1, 3.2, 3.7_

  - [] 8.2 Add basic integration and end-to-end tests
    - Create integration tests for complete weather data flow
    - Implement end-to-end tests for user journey with CloudWatch synthetics
    - Create basic infrastructure deployment tests
    - Write basic test automation scripts with cleanup
    - _Requirements: 2.4, 3.2_

  - [] 8.3 Add cache header validation tests
    - Create automated tests to verify Cache-Control headers are properly set
    - Test that static assets return max-age=900 in response headers
    - Validate cache behavior across different asset types (HTML, CSS, JS, images)
     - _Requirements: 1.4, 2.4_

  - [] 8.4 Fix CI/CD deployment path issues
    - Resolve frontend build path problems in CI/CD environments where working directory structure differs
    - Update Terraform frontend module to handle different working directory structures and missing directories
    - Add proper error handling and path validation for frontend build process
    - Ensure frontend directory and package.json are found correctly in CI/CD pipelines
    - Test build process works in both local development and CI/CD environments
    - _Requirements: 3.1, 3.4_

  - [] 8.5 Update unit tests for simplified Lambda implementation
    - Update existing unit tests to work with the simplified embedded Lambda handler
    - Remove tests for separate weather service modules (api_client, cache, processor, etc.)
    - Create focused tests for the main Lambda handler functions
    - Test weather data fetching, processing, response formatting, and DynamoDB caching
    - Ensure tests cover error handling, cache hits/misses, and edge cases
    - _Requirements: 2.4, 3.2_

  - [] 8.6 Add frontend error loop prevention safeguards
    - Implement circuit breaker pattern in useWeatherData hook to prevent infinite retry loops
    - Add exponential backoff with maximum delay caps for failed requests
    - Implement request rate limiting to prevent rapid successive API calls on errors
    - Add error threshold detection to disable auto-retry after consecutive failures
    - Create user-friendly error states that prevent automatic retry loops
    - _Requirements: 1.2, 2.1_

  - [] 8.7 Configure reasonable Lambda concurrency limits
    - Set Lambda reserved concurrency to 5 concurrent executions (reasonable for weather API)
    - Update backend module variables to reflect appropriate concurrency limits
    - Add documentation explaining concurrency limits and cost implications
    - Ensure concurrency limits prevent runaway costs while maintaining service availability
    - Test concurrency limits under load to ensure proper throttling behavior
    - _Requirements: 3.6, 3.8_

  - [] 8.8 Implement dynamic cache-control headers in Lambda function
    - Update Lambda handler to set cache-control: max-age=60 for successful weather API responses
    - Set cache-control: max-age=0 for failed weather API responses or error conditions
    - Ensure cache-control headers are properly included in HTTP response headers
    - Test cache-control behavior for both success and failure scenarios
    - _Requirements: 2.5, 2.6_

  - [] 8.9 Implement lastUpdated timestamp handling in Lambda function
    - Update Lambda handler to include lastUpdated timestamp in all API responses
    - Use weather API timestamp when available in the met.no API response
    - Fall back to DynamoDB cache timestamp when weather API timestamp is not provided
    - Ensure timestamp is in ISO 8601 format for consistent frontend display
    - Test timestamp handling for both fresh API calls and cached responses
    - _Requirements: 2.7, 2.8_

  - [] 8.10 Update frontend to display lastUpdated timestamp
    - Modify weather display components to show the lastUpdated timestamp from API responses
    - Format timestamp for user-friendly display (e.g., "Last updated: 2 minutes ago")
    - Handle cases where lastUpdated is null or missing
    - Ensure timestamp display is responsive and accessible
    - _Requirements: 2.7_

- [] 9. Generate documentation and cost analysis
  - [] 9.1 Create architecture diagrams
    - Generate AWS architecture diagram using MCP diagram server
    - Create sequence diagrams for weather data flow
    - Add deployment flow diagrams
    - Include diagrams in main README.md
    - _Requirements: 3.7_

  - [] 9.2 Perform cost analysis and optimization
    - Use AWS Labs Pricing MCP server for cost calculations
    - Compare costs across eu-west-1, eu-central-1, eu-north-1 regions
    - Create cost projections for staging and production environments
    - Document top three cost optimization opportunities
    - Include cost analysis in main README.md
    - _Requirements: 3.7_

- [] 10. Finalize project documentation
  - Create crisp and clear README.md with TL;DR section
  - Add executive summary for project stakeholders
  - Create basic deployment guide and troubleshooting documentation
  - Write operational runbooks for maintenance
  - Add basic examples for CI/CD integration and how to configure relevant variables
  - _Requirem&amp;lt;/textarea&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Implementation Plan

- [] 1. Set up project structure and configuration
  - Create Terraform module directory structure following best practices
  - Set up Python virtual environment with pyenv for application development
  - Configure pre-commit hooks for Terraform validation and formatting
  - Create basic project documentation structure with docs/ directory
  - _Requirements: 3.1, 3.2_

- [] 2. Implement simplified Lambda weather service
  - [] 2.1 Create embedded weather service in Lambda handler
    - Implement weather data fetching directly in lambda_handler.py using urllib
    - Define city configuration with coordinates for Oslo, Paris, London, Barcelona
    - Create weather data processing and transformation logic embedded in handler
    - Implement proper User-Agent header with configurable company website
    - Add rate limiting with simple delays between API calls
    - _Requirements: 2.2, 2.3, 1.1_

  - [] 2.2 Implement weather API integration and processing
    - Create fetch_weather_data function for met.no API calls
    - Implement extract_tomorrow_forecast for parsing API responses
    - Add weather condition mapping and error handling
    - Create process_city_weather for individual city processing
    - Implement get_weather_summary for all cities with delay between calls
    - _Requirements: 2.2, 1.2_

- [] 3. Build Lambda function infrastructure
  - [] 3.1 Create simplified Lambda function handler
    - Implement main Lambda handler with embedded weather service
    - Add environment variable configuration for company website
    - Implement proper error handling and logging with standardized responses
    - Create /health endpoint for monitoring with environment information
    - Add CORS support and OPTIONS request handling
    - _Requirements: 2.1, 2.4, 3.6_

  - [] 3.2 Add DynamoDB caching to simplified Lambda handler
    - Implement DynamoDB caching directly in the lambda_handler.py file
    - Add cache check before API calls and cache storage after successful API responses
    - Implement 1-hour TTL (3600 seconds) for cached weather data
    - Add error handling for DynamoDB operations with fallback to API calls
    - Use boto3 client for DynamoDB operations embedded in the handler
    - _Requirements: 2.2, 1.2, 3.6_

- [] 4. Update Terraform infrastructure for simplified approach
  - [] 4.1 Maintain DynamoDB table configuration for caching
    - Keep existing DynamoDB table from Terraform backend module
    - Maintain DynamoDB-related IAM permissions for Lambda role
    - Ensure DynamoDB table name is passed to Lambda via environment variable
    - Keep TTL configuration for 1-hour cache expiration
    - Maintain existing tests for DynamoDB validation
    - _Requirements: 3.1, 3.6, 3.8_

  - [] 4.2 Update Lambda function Terraform module for simplified deployment
    - Update Terraform configuration for simplified Lambda function
    - Maintain DynamoDB environment variables (table name) and permissions
    - Keep COMPANY_WEBSITE environment variable configuration
    - Maintain X-Ray tracing and CloudWatch logging
    - Keep IAM role with DynamoDB permissions for caching
    - _Requirements: 3.1, 3.4, 3.6, 3.8_

  - [] 4.3 Maintain API Gateway configuration
    - Keep existing API Gateway REST API configuration
    - Maintain CORS settings and rate limiting
    - Keep Lambda integration with proper error handling
    - Maintain CloudWatch logging configuration
    - Keep existing tests for the API Gateway setup
    - _Requirements: 3.1, 3.5, 3.6_

- [] 5. Create frontend application
  - [] 5.1 Build responsive weather display components
    - Create React components for weather card display
    - Implement responsive grid layout for four cities
    - Add loading states and error handling UI
    - Ensure mobile-optimized design with proper breakpoints
    - _Requirements: 1.1, 1.2, 1.3, 2.1_

  - [] 5.2 Implement API integration and state management
    - Create API client for backend weather service
    - Implement data fetching with error handling and retries
    - Add browser-side caching strategy respecting 1-hour backend cache
    - Create loading and error state management
    - _Requirements: 1.2, 2.1, 2.2_

  - [] 5.3 Add weather icons and styling
    - Implement weather condition icon mapping
    - Create CSS styling for responsive design
    - Add animations and transitions for better UX
    - Ensure accessibility compliance (WCAG)
    - _Requirements: 1.1, 1.3, 2.1_

  - [] 5.4 Optimize frontend build for caching
    - Configure build process to generate static assets optimized for 15-minute caching
    - Ensure proper file naming and versioning for cache busting when needed
    - Validate that all static assets (HTML, CSS, JS, images) are properly configured
    - _Requirements: 1.2, 1.4_

- [] 6. Configure static hosting infrastructure
  - [] 6.1 Create S3 bucket for static hosting
    - Implement Terraform module for S3 bucket configuration
    - Configure bucket policies for static website hosting
    - Set up versioning and lifecycle policies
    - Add proper IAM permissions for deployment
    - Write basic tests for the S3 configuration
    - _Requirements: 3.1, 3.4, 3.6_

  - [] 6.2 Set up CloudFront distribution
    - Create Terraform module for CloudFront CDN
    - Configure cache behaviors and TTL settings
    - Set up origin failover for high availability
    - Add security headers and HTTPS redirection
    - Write basic tests for the Cloudfront configuration
    - _Requirements: 1.2, 3.1, 3.8_

  - [] 6.4 Configure CloudFront price class and optimization settings
    - Update CloudFront distribution to use price class 100 (PriceClass_100)
    - Configure allowed HTTP methods to GET, HEAD, and OPTIONS only
    - Set up caching policy configuration based on query parameters
    - Configure default TTL to 900 seconds (15 minutes)
    - Ensure coverage includes Europe and United States edge locations
    - Validate cost optimization while maintaining performance for target regions
    - Update Terraform configuration with appropriate price_class, allowed_methods, and caching parameters
    - Test CloudFront distribution functionality with new configuration
    - _Requirements: 3.9, 3.10, 3.11, 3.12_

  - [] 6.3 Configure Cache-Control headers for static content
    - Configure S3 bucket metadata to set Cache-Control: max-age=900 for all static assets
    - Update CloudFront cache behaviors to respect and forward Cache-Control headers
    - Ensure consistent 15-minute caching for HTML, CSS, JavaScript, and image files
    - _Requirements: 1.2, 1.4_

- [] 7. Implement monitoring and observability
  - [] 7.1 Create simple and intuitive CloudWatch dashboard and alarms
    - Implement Terraform module for CloudWatch dashboard
    - Configure the most important alarms for Lambda errors, API Gateway 5xx, and DynamoDB throttling
    - Set up custom metrics for weather API success rates
    - Add log retention policies (180 days)
    - _Requirements: 3.6, 3.7_

  - [] 7.2 Set up AWS Budget and cost monitoring
    - Create Terraform module for AWS Budget with Service tag filter
    - Configure budget alerts for cost thresholds
    - Implement simple and intuitive cost monitoring Cloudwatch dashboard
    - _Requirements: 3.3, 3.7_

- [] 8. Create deployment and testing automation
  - [] 8.1 Implement Terraform module packaging
    - Create main Terraform module with all sub-modules
    - Configure variable definitions and outputs
    - Add module documentation with terraform-docs
    - Create examples/ directory with usage examples
    - _Requirements: 3.1, 3.2, 3.7_

  - [] 8.2 Add basic integration and end-to-end tests
    - Create integration tests for complete weather data flow
    - Implement end-to-end tests for user journey with CloudWatch synthetics
    - Create basic infrastructure deployment tests
    - Write basic test automation scripts with cleanup
    - _Requirements: 2.4, 3.2_

  - [] 8.3 Add cache header validation tests
    - Create automated tests to verify Cache-Control headers are properly set
    - Test that static assets return max-age=900 in response headers
    - Validate cache behavior across different asset types (HTML, CSS, JS, images)
     - _Requirements: 1.4, 2.4_

  - [] 8.4 Fix CI/CD deployment path issues
    - Resolve frontend build path problems in CI/CD environments where working directory structure differs
    - Update Terraform frontend module to handle different working directory structures and missing directories
    - Add proper error handling and path validation for frontend build process
    - Ensure frontend directory and package.json are found correctly in CI/CD pipelines
    - Test build process works in both local development and CI/CD environments
    - _Requirements: 3.1, 3.4_

  - [] 8.5 Update unit tests for simplified Lambda implementation
    - Update existing unit tests to work with the simplified embedded Lambda handler
    - Remove tests for separate weather service modules (api_client, cache, processor, etc.)
    - Create focused tests for the main Lambda handler functions
    - Test weather data fetching, processing, response formatting, and DynamoDB caching
    - Ensure tests cover error handling, cache hits/misses, and edge cases
    - _Requirements: 2.4, 3.2_

  - [] 8.6 Add frontend error loop prevention safeguards
    - Implement circuit breaker pattern in useWeatherData hook to prevent infinite retry loops
    - Add exponential backoff with maximum delay caps for failed requests
    - Implement request rate limiting to prevent rapid successive API calls on errors
    - Add error threshold detection to disable auto-retry after consecutive failures
    - Create user-friendly error states that prevent automatic retry loops
    - _Requirements: 1.2, 2.1_

  - [] 8.7 Configure reasonable Lambda concurrency limits
    - Set Lambda reserved concurrency to 5 concurrent executions (reasonable for weather API)
    - Update backend module variables to reflect appropriate concurrency limits
    - Add documentation explaining concurrency limits and cost implications
    - Ensure concurrency limits prevent runaway costs while maintaining service availability
    - Test concurrency limits under load to ensure proper throttling behavior
    - _Requirements: 3.6, 3.8_

  - [] 8.8 Implement dynamic cache-control headers in Lambda function
    - Update Lambda handler to set cache-control: max-age=60 for successful weather API responses
    - Set cache-control: max-age=0 for failed weather API responses or error conditions
    - Ensure cache-control headers are properly included in HTTP response headers
    - Test cache-control behavior for both success and failure scenarios
    - _Requirements: 2.5, 2.6_

  - [] 8.9 Implement lastUpdated timestamp handling in Lambda function
    - Update Lambda handler to include lastUpdated timestamp in all API responses
    - Use weather API timestamp when available in the met.no API response
    - Fall back to DynamoDB cache timestamp when weather API timestamp is not provided
    - Ensure timestamp is in ISO 8601 format for consistent frontend display
    - Test timestamp handling for both fresh API calls and cached responses
    - _Requirements: 2.7, 2.8_

  - [] 8.10 Update frontend to display lastUpdated timestamp
    - Modify weather display components to show the lastUpdated timestamp from API responses
    - Format timestamp for user-friendly display (e.g., "Last updated: 2 minutes ago")
    - Handle cases where lastUpdated is null or missing
    - Ensure timestamp display is responsive and accessible
    - _Requirements: 2.7_

- [] 9. Generate documentation and cost analysis
  - [] 9.1 Create architecture diagrams
    - Generate AWS architecture diagram using MCP diagram server
    - Create sequence diagrams for weather data flow
    - Add deployment flow diagrams
    - Include diagrams in main README.md
    - _Requirements: 3.7_

  - [] 9.2 Perform cost analysis and optimization
    - Use AWS Labs Pricing MCP server for cost calculations
    - Compare costs across eu-west-1, eu-central-1, eu-north-1 regions
    - Create cost projections for staging and production environments
    - Document top three cost optimization opportunities
    - Include cost analysis in main README.md
    - _Requirements: 3.7_

- [] 10. Finalize project documentation
  - Create crisp and clear README.md with TL;DR section
  - Add executive summary for project stakeholders
  - Create basic deployment guide and troubleshooting documentation
  - Write operational runbooks for maintenance
  - Add basic examples for CI/CD integration and how to configure relevant variables
  - _Requirem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the IDE you will see an option to trigger to start tasks:&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%2Fkd3y2917fuu9v12sxtu5.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%2Fkd3y2917fuu9v12sxtu5.png" width="800" height="387"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can either trigger them one by one or ask Kiro in the chat to get started.&lt;/p&gt;

&lt;p&gt;Let’s start the first task.&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%2Fwyj4qwp5srqb1qce6v1j.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%2Fwyj4qwp5srqb1qce6v1j.png" width="596" height="157"&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%2F9vw5vf7jx51plvdthapr.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%2F9vw5vf7jx51plvdthapr.png" width="799" height="466"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can see that Kiro is setting up the structure according to what’s stated on the external website &lt;a href="http://terraform-best-practices.com" rel="noopener noreferrer"&gt;terraform-best-practices.com&lt;/a&gt;, as requested. Nice.&lt;/p&gt;

&lt;p&gt;Like with Amazon Q Developer, you can approve or let Kiro trust specific tools and commands:&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%2Ffx69mhg425ajrf0q0acy.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%2Ffx69mhg425ajrf0q0acy.png" width="800" height="319"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After a short while the first task is complete!&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%2Fjm9crsv1mftobi5fqe33.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%2Fjm9crsv1mftobi5fqe33.png" width="800" height="602"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I did note that Terraform AWS provider version 5 was installed. The latest one is major version 6.7.0, so I updated the Tech Steering specification and asked Kiro to refresh.&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%2F1866g5zkgurt0r3vsg9a.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%2F1866g5zkgurt0r3vsg9a.png" width="799" height="259"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Kiro refreshed the context guidelines, found what to update, performed the changes and ran checks and pre-commit to verify it’s working as expected.&lt;/p&gt;

&lt;p&gt;Then we move on to Task 2: Implement core Python service, and so on.&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%2Fgz05e3eddyravi0lawny.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%2Fgz05e3eddyravi0lawny.png" width="800" height="322"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is how the Summary of implementation looks like for task 7.&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%2Favbgn0jxpx7f2mtx4jho.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%2Favbgn0jxpx7f2mtx4jho.png" width="800" height="642"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see the main difference between the traditional vibe/CLI approach is that the spec-driven workflow keeps the steering and requirements up to date, to persist the context. This makes the process a lot more predictable, and possible to collaborate on in a team.&lt;/p&gt;

&lt;p&gt;Keeping the specifications version controlled along with the application codebase makes it easy to track changes as changes are committed.&lt;/p&gt;

&lt;p&gt;Depending on your team development workflow, each feature could be organized as a Spec, containing relevant user stories.&lt;/p&gt;

&lt;p&gt;Kiro generated a comprehensive local testing suite. It turned out to be more complex than I think is necessary, with some tests being flaky. I asked Kiro to focus on testing the core functionality and remove brittle and complex tests. A reflection here is that I did not specify the desired test approach in detail in my steering context.&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%2F9svkb8prnonms4tmctgd.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%2F9svkb8prnonms4tmctgd.png" width="800" height="532"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here are the diagrams the AWS Diagrams MCP server helped create:&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%2Frnpqla0bzjo6soyyxrdz.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%2Frnpqla0bzjo6soyyxrdz.png" width="800" height="731"&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%2F9xh1l36p8olch65nbui0.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%2F9xh1l36p8olch65nbui0.png" width="290" height="1024"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying the final solution
&lt;/h2&gt;

&lt;p&gt;I included the module definition in my existing Github Actions CI/CD Terraform codebase:&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;textarea tabindex="-1" aria-hidden="true" readonly&amp;gt;module "weather_forecast_app" {
  source = "git::https://github.com/haakond/terraform-aws-weather-forecast.git?ref=COMMIT-SHA"
  project_name = "weather-forecast-app"
  environment = "prod"
  aws_region = "eu-west-1"
  weather_service_identification_domain = "youramazingwebsite.com"
}&amp;lt;/textarea&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module "weather_forecast_app" {
  source = "git::https://github.com/haakond/terraform-aws-weather-forecast.git?ref=COMMIT-SHA"
  project_name = "weather-forecast-app"
  environment = "prod"
  aws_region = "eu-west-1"
  weather_service_identification_domain = "youramazingwebsite.com"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I experienced a few Terraform errors that Kiro wasn’t able to catch before terraform plan and apply. Issues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CloudWatch Logs groups defined with the same name in two sub-modules&lt;/li&gt;
&lt;li&gt;Missing API Gateway Account configuration for CloudWatch Logs&lt;/li&gt;
&lt;li&gt;Missing deploy process for the React frontend to Amazon S3

&lt;ul&gt;
&lt;li&gt;Since this is fairly static app I decided to keep it along with the infrastructure code, for simplicity.&lt;/li&gt;
&lt;li&gt;For production frontend applications I would set up a dedicated CI/CD pipeline to deploy only the frontend codebase. &lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;An overly complex Reach frontend application, which I asked Kiro to simplify.&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;With some additional prompting assistance from Kiro I was able to resolve the issues and end up with a fully working deployment!&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%2Fawkmuazmrkrljtb9s7ch.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%2Fawkmuazmrkrljtb9s7ch.png" width="800" height="700"&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%2Fncxh8z9zi5sqyl44q21p.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%2Fncxh8z9zi5sqyl44q21p.png" width="799" height="580"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;End result:&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%2F2q2ykfggwix67y8t6ipt.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%2F2q2ykfggwix67y8t6ipt.png" width="800" height="443"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Workflow for adding a new feature
&lt;/h3&gt;

&lt;p&gt;Here’s one possible suggested approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;git clone into a new feature branch&lt;/li&gt;
&lt;li&gt;Specify

&lt;ul&gt;
&lt;li&gt;For a major new feature: create a new Specification (requirements, design, tasks)&lt;/li&gt;
&lt;li&gt;For a minor improvement: Incorporate into an existing Specification&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Implement requirements&lt;/li&gt;

&lt;li&gt;Create pull request&lt;/li&gt;

&lt;li&gt;Review and merge to main&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Learnings and key takeaways
&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%2Fvtqlaxy492og335cwwje.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%2Fvtqlaxy492og335cwwje.png" width="538" height="34"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If you do manual changes outside of Kiro, the specs (Requirements, Design, Tasks) will deviate and confuse Kiro. Stick to the Kiro workflow, and ask Kiro to refresh what you did. Kiro will backport into the specs.

&lt;ul&gt;
&lt;li&gt;If during the preview period you keep hitting Kiro’s usage limit, a workaround can be to get Amazon Q Developer help you when troubleshooting, to save interaction tokens. Just make sure that you tell Kiro which areas has changed to get the specs up-to-date.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Separate product feature specifications from common steering context.&lt;/li&gt;
&lt;li&gt;Kiro has a tendency to add more comprehensive and complex testing procedures and documentation than a human would appreciate. Be explicit about keeping things simple and focus on the core functionality.&lt;/li&gt;
&lt;li&gt;To ensure the suggested tests are relevant and follows your company practice, add a detailed Agent Steering document for testing.&lt;/li&gt;
&lt;li&gt;Organize specs by feature, to be able to work independently without conflicts or affecting other areas. This can also reduce the blast radius in case of unexpected changes, plus it reduces the context size for the agent. &lt;/li&gt;
&lt;li&gt;Keep specs and user stories in version control along with your application. If you perform traditional manual changes, give Kiro a hint to have the design and requirements updated accordingly.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Reflections on context
&lt;/h3&gt;

&lt;p&gt;Traditionally, the environment specific context are a company’s tech stack, policies and guidelines combined with the experience of experienced software engineers. This is information known and acquired in your setting and is normally not included in (Jira) User Stories. However, coding agents by default do not posess this context information. The closest thing may be the possibility to configure Amazon Q Developer with custom repositories, so that it can learn about company specific coding standards, libraries etc.&lt;/p&gt;

&lt;p&gt;Spec driven development with Kiro now forces this context information to be defined as Agent Steering resources. Teams can organize workshops to document their guiding tenets, principles, organized in Git repo and iterated as the information evolves. Perhaps your team have something similar documented in a company wiki already?&lt;/p&gt;

&lt;p&gt;I think we need to help AI coding companions build the same mental framework we’d give to a human colleague during onboarding and code review. Kiro solves this by checking in your specifications to Git. Consider creating a Kiro app bootstrap repository or include common steering as a Git submodule, package reference or similar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Kiro pricing
&lt;/h2&gt;

&lt;p&gt;When Kiro becomes Generally Available, there will be different tiers available to match your level of usage.&lt;/p&gt;

&lt;p&gt;Vibe Requests cover any agentic operation in Kiro that does not involve execution of a Spec Task.&lt;/p&gt;

&lt;p&gt;You start with Vibe requests to create requirements, design documents and tasks.&lt;/p&gt;

&lt;p&gt;One Vibe Request typically equals one message or prompt, while one Spec Requests equals executing a single Spec Task.&lt;/p&gt;

&lt;p&gt;For more information see &lt;a href="https://kiro.dev/blog/understanding-kiro-pricing-specs-vibes-usage-tracking/" rel="noopener noreferrer"&gt;https://kiro.dev/blog/understanding-kiro-pricing-specs-vibes-usage-tracking/&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;My personal experience is that I really appreciate the spec-driven development process. Kiro is a game-changer which can significantly boost what builders are able to produce. There are no more valid excuses for avoiding sufficient test coverage or struggling with even a nice looking frontend app!&lt;/p&gt;

&lt;p&gt;Kiro is not just a new tool, it is a new workflow; AI-assisted, spec-driven development which can incorporate mature engineering practices, and my initial evaluation leaves me thinking that AI is starting to grow up and become more professional. As a bonus, Kiro forces you to write better specifications, which can make it easier to establish a common alignment in a team, and while onboarding new team members.&lt;/p&gt;

&lt;p&gt;This approach is a giant leap forward compared to coding assistants pre H2 2025. &lt;a href="https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/context-project-rules.html" rel="noopener noreferrer"&gt;Project Rules&lt;/a&gt; and &lt;a href="https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/customizations.html" rel="noopener noreferrer"&gt;Customizations&lt;/a&gt; in &lt;a href="https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/what-is.html" rel="noopener noreferrer"&gt;Amazon Q Developer&lt;/a&gt; were a step in the right direction, but Kiro brings a sought-after structure and consistency that in my opinion not only is nice, but necessary, in a professional context. Yes, there are still some bugs and quirks (Kiro is at time of writing still in limited preview) but I am optimistic that this technology and the underlying Large Language Models will mature and produce results of increasingly higher quality and predictability over the coming months. Earlier, my experience was that code assistant suggestions could get me around ~70% up to speed, with ~30% traditional authoring for preciseness. Kiro boosts this to maybe ~85%+. I would claim that return on investment on the Kiro license can be achieved pretty fast.&lt;/p&gt;

&lt;p&gt;I would still prefer a knowledgeable human in the loop reviewing Pull Requests and making the final decision for changes, that is until Agentic AI has matured a bit more. It will be exciting to see how this field evolves during the next couple of years.&lt;/p&gt;

&lt;p&gt;I encourage you to try out spec-driven development with your colleagues and warmly welcome Kiro as your new team member.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://kiro.dev/" rel="noopener noreferrer"&gt;https://kiro.dev/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kiro.dev/blog/introducing-kiro/" rel="noopener noreferrer"&gt;https://kiro.dev/blog/introducing-kiro/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kiro.dev/blog/kiro-and-the-future-of-software-development/" rel="noopener noreferrer"&gt;https://kiro.dev/blog/kiro-and-the-future-of-software-development/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.anthropic.com/claude/sonnet" rel="noopener noreferrer"&gt;Anthropic Claude Sonnet&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href="https://builder.aws.com/content/30HonEOE2KzaYCYlyDRdbE25yxz/ai-driven-development-life-cycle-reimagining-software-engineering" rel="noopener noreferrer"&gt;AWS Builder Center blog: AI-Driven Development Life Cycle: Reimagining Software Engineering&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Header image courtesy of &lt;a href="https://aws.amazon.com/ai/generative-ai/nova/" rel="noopener noreferrer"&gt;Amazon Nova Canvas 1.0&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The post &lt;a href="https://hedrange.com/2025/08/11/how-to-use-kiro-for-ai-assisted-spec-driven-development/" rel="noopener noreferrer"&gt;How to use Kiro for AI assisted spec-driven development&lt;/a&gt; first appeared on &lt;a href="https://hedrange.com/" rel="noopener noreferrer"&gt;Håkon Eriksen Drange&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>articles</category>
    </item>
    <item>
      <title>Extensive reporting of Well-Architected Maturity</title>
      <dc:creator>Håkon Eriksen Drange</dc:creator>
      <pubDate>Sun, 20 Jul 2025 07:59:45 +0000</pubDate>
      <link>https://dev.to/haakoned/extensive-reporting-of-well-architected-maturity-of4</link>
      <guid>https://dev.to/haakoned/extensive-reporting-of-well-architected-maturity-of4</guid>
      <description>&lt;h5&gt;
  
  
  Table of contents
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;How to generate a Well-Architected Compliance report&lt;/li&gt;
&lt;li&gt;What a Well-Architected Framework Compliance report looks like&lt;/li&gt;
&lt;li&gt;How the reporting feature works&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;li&gt;Feedback and contributions&lt;/li&gt;
&lt;li&gt;Resources&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In the post &lt;a href="https://hedrange.com/2025/04/15/how-to-measure-well-architected-maturity/" rel="noopener noreferrer"&gt;How to measure Well-Architected maturity&lt;/a&gt; we explored how extensive insight into cloud infrastructure posture could help accelerate the &lt;em&gt;Measure&lt;/em&gt; phase, leaving more time to &lt;em&gt;Learn&lt;/em&gt; and discuss opportunities for &lt;em&gt;Improvement&lt;/em&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%2F4ydvlnkdryn7v3vbu962.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%2F4ydvlnkdryn7v3vbu962.png" alt="AWS WAFR process" width="300" height="292"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure – Well-Architected Framework review cycle courtesy of AWS&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A key factor was to make the data available while performing a review in the Well-Architected Tool. After discussing the solution with colleagues and customers I realized that the valuable data points weren’t exposed to their full potential within only the Notes field in the Well-Architected Tool. Key constraints are that the Notes field is limited to plain text and a maximum of 2000 characters. Valuable resource identifiers and tags had to be capped, which made it harder to easily identify the applicable resources. Could there be a better way?&lt;/p&gt;

&lt;p&gt;I decided to develop an additional AWS Lambda function called &lt;code&gt;well_architected_report_generator&lt;/code&gt;. The main purpose of this Lambda function is to collect all the available data points from various sources and generate a report in HTML format stored in Amazon Simple Storage Service (S3). When performing Well-Architected Framework Reviews, you are most likely already logged in to the AWS Console to access the Well-Architected Tool, so opening an additional tab with S3 could be useful. Thanks to Amazon Q Developer the HTML and CSS came out pretty good, too!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Current data sources:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS Config Conformance Packs&lt;/li&gt;
&lt;li&gt;AWS Trusted Advisor checks (available checks depends on active AWS Support Plan)&lt;/li&gt;
&lt;li&gt;Resource Tag: Name&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  How to generate a Well-Architected Compliance report
&lt;/h2&gt;

&lt;p&gt;Deploy the Terraform module as also described in &lt;a href="https://hedrange.com/2025/04/15/how-to-measure-well-architected-maturity/" rel="noopener noreferrer"&gt;How to measure Well-Architected Maturity&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Please note that three new (optional) Terraform module variables have been introduced:&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;textarea tabindex="-1" aria-hidden="true" readonly&amp;gt;variable "deploy_aws_config_recorder" {
  description = "Set to true to deploy an AWS Config Recorder. If you already have a customer managed AWS Config recorder in the desired region, set to false. AWS supports only one customer managed configuration recorder for each account for each AWS Region."
  type = bool
  default = true
}

variable "reports_bucket_name_prefix" {
  description = "Prefix for the S3 bucket name that stores Well-Architected compliance reports"
  type = string
  default = "well-architected-compliance-reports"
}

variable "reports_retention_days" {
  description = "Number of days to retain non-current versions of reports in the S3 bucket"
  type = number
  default = 90
}&amp;lt;/textarea&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;variable "deploy_aws_config_recorder" {
  description = "Set to true to deploy an AWS Config Recorder. If you already have a customer managed AWS Config recorder in the desired region, set to false. AWS supports only one customer managed configuration recorder for each account for each AWS Region."
  type = bool
  default = true
}

variable "reports_bucket_name_prefix" {
  description = "Prefix for the S3 bucket name that stores Well-Architected compliance reports"
  type = string
  default = "well-architected-compliance-reports"
}

variable "reports_retention_days" {
  description = "Number of days to retain non-current versions of reports in the S3 bucket"
  type = number
  default = 90
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At the time of writing AWS only supports one customer managed AWS Config recorder in each region. If you already have one, set this value to false, for re-use.&lt;/p&gt;

&lt;p&gt;If you would like to retain the reports for longer than the default value of 90 days you may set the desired value accordingly.&lt;/p&gt;

&lt;p&gt;A minimal example module call if you already have a customer managed AWS Config recorder in your region and you prefer to retain the report files for 400 days may look like this:&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;textarea tabindex="-1" aria-hidden="true" readonly&amp;gt;module "well_architected_config_conformance_pack" {
  source = "git::https://github.com/soprasteria/terraform-aws-wellarchitected-conformance.git?ref=&amp;amp;lt;DESIRED-COMMIT-SHA&amp;amp;gt;"
  deploy_aws_config_recorder = false
  reports_retention_days = 400
}&amp;lt;/textarea&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module "well_architected_config_conformance_pack" {
  source = "git::https://github.com/soprasteria/terraform-aws-wellarchitected-conformance.git?ref=&amp;lt;DESIRED-COMMIT-SHA&amp;gt;"
  deploy_aws_config_recorder = false
  reports_retention_days = 400
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait 24 hours for data to be collected and aggregated.&lt;/p&gt;

&lt;p&gt;Then, in the AWS Console, go to AWS Lambda and locate the function well_architected_report_generator.&lt;br&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.amazonaws.com%2Fuploads%2Farticles%2Ff18rgg3ri86mfvm8kmkb.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%2Ff18rgg3ri86mfvm8kmkb.png" width="800" height="112"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Create a new Test function with a JSON payload of workload_id for the Well-Architected Tool (not the complete ARN, just the last part) and dry_run. Example payload:&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;textarea tabindex="-1" aria-hidden="true" readonly&amp;gt;{
  "workload_id": "141970ea95fd5b4329cyh05502659f39",
  "dry_run": 0
}&amp;lt;/textarea&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "workload_id": "141970ea95fd5b4329cyh05502659f39",
  "dry_run": 0
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hit Test. Execution may take a minute or two, depending on the amount of AWS infrastructure resources deployed in the AWS account.&lt;/p&gt;

&lt;p&gt;The Cloud Watch Logs output will let you know which AWS Support plan is detected and where you can find the produced report.&lt;br&gt;&lt;br&gt;
&lt;em&gt;Starting Well-Architected Report Generator&lt;br&gt;Collecting compliance data from AWS Config&lt;br&gt;Trusted Advisor compliance status mapping&lt;br&gt;AWS Business or Enterprise Support is/is not enabled&lt;br&gt;Successfully uploaded report to s3://well-architected-compliance-reports-123456789012/Reports/well_architected_compliance_report_%timestamp%.html&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Navigate to Amazon S3, find the most recent report by sorting on the Last modified column, check the box to the left of Name and click Open to access the report in a new browser tab.&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%2Fbbpa7awwl0iff5v7xu7s.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%2Fbbpa7awwl0iff5v7xu7s.png" width="799" height="192"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What a Well-Architected Framework Compliance report looks like
&lt;/h2&gt;

&lt;p&gt;The produced report contains information about the time of generation, the AWS Account ID, region and detected AWS Support plan.&lt;/p&gt;

&lt;p&gt;Example report 1, an AWS account with Basic Support:&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%2Fhzk95j5y2nr8aeivul8u.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%2Fhzk95j5y2nr8aeivul8u.png" width="800" height="203"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Example report 2, an AWS account with Enterprise Support:&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%2Feo1h1zble6joaxy9hjos.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%2Feo1h1zble6joaxy9hjos.png" width="800" height="214"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The report may contain a lot of detailed information, so an Executive Summary is provided as a high-level overview.&lt;/p&gt;

&lt;p&gt;Example report 1:&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%2F9w7dbn7o6mge2sx6mqaq.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%2F9w7dbn7o6mge2sx6mqaq.png" width="800" height="471"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Example report 2:&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%2F7996xelhntozr5ty7zjy.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%2F7996xelhntozr5ty7zjy.png" width="800" height="487"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next a Table of Contents is provided which includes an overview of the available Well-Architected Framework pillars and questions.&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%2Fpwio2wz1pwio2j5de1ml.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%2Fpwio2wz1pwio2j5de1ml.png" width="800" height="408"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each question includes relevant context information from the Well-Architected Framework and link to further guidance.&lt;/p&gt;

&lt;p&gt;Here is the second question in the Security pillar: “How do you manage identifies for people and machines?”&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%2Fv9218687xhophbmpmtmf.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%2Fv9218687xhophbmpmtmf.png" width="799" height="202"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For AWS Accounts with available AWS Premium Support, relevant Trusted Advisor check information is included.&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%2Ffjt06ciftyaxq85mnlou.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%2Ffjt06ciftyaxq85mnlou.png" width="800" height="359"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Relevant AWS Config checks and their status are displayed, along with the detected Resource Type, Resource ID and Tag Name, if available, for easy identification and discussion.&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%2Fxtrckn1143w8vf85adxn.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%2Fxtrckn1143w8vf85adxn.png" width="800" height="358"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For Reliability Pillar question number 9, Trusted Advisor checks indicates that RDS backups are enabled for all clusters, but there is at least one S3 bucket where replication is not configured.&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%2Fvkzqu4w802xe4k83rvhz.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%2Fvkzqu4w802xe4k83rvhz.png" width="800" height="477"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The next section lists all detected AWS resources, including Resource ID, Status and Tag Name, as available (certain information has been obfuscated on purpose).&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%2Foklgxw93lnrylmzlbpl3.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%2Foklgxw93lnrylmzlbpl3.png" width="800" height="150"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Moving to Cost Optimization pillar question number three: “How do you monitor cost and usage?” actually has no official Trusted Advisor checks associated, but the Terraform module fills the gap with custom AWS Config checks.&lt;/p&gt;

&lt;p&gt;Based on this we can easily see that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Cost Anomaly Detection is configured with a Cost monitor.&lt;/li&gt;
&lt;li&gt;There is at least one AWS Budget configured with alert subscriptions.&lt;/li&gt;
&lt;li&gt;There are no EC2 instances not in any Auto Scaling Groups.&lt;/li&gt;
&lt;li&gt;The AWS account is a member of AWS Organizations and a Tag Policy is in effect.&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%2F11m5x6m3ty2mhhi8s4ze.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%2F11m5x6m3ty2mhhi8s4ze.png" width="800" height="604"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How the reporting feature works
&lt;/h2&gt;

&lt;p&gt;Let’s take a closer look into the underlying logic of the &lt;code&gt;well_architected_report_generator&lt;/code&gt; Lambda function.&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%2F6uzoxbqcksobnme9omjo.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%2F6uzoxbqcksobnme9omjo.png" width="800" height="808"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Diagram created by the help of AWS Diagram and Documentation MCP Servers&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;After the Terraform module is deployed AWS Config needs 24 hours to collect and aggregate all data points. Then when the Lambda function is triggered, the following happens:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The starting point is a &lt;a href="https://docs.aws.amazon.com/wellarchitected/latest/userguide/workloads.html" rel="noopener noreferrer"&gt;Workload&lt;/a&gt; in the &lt;a href="https://docs.aws.amazon.com/wellarchitected/latest/userguide/intro.html" rel="noopener noreferrer"&gt;Well-Architected Tool&lt;/a&gt;. It’s not necessary to answer all questions first, you can do that with your team after the initial report has been generated. Every question in each pillar will then be mapped to relevant AWS Config and Trusted Advisor checks. Note: There is currently not a 100% 1-1 mapping with AWS Config and Trusted Advisor checks, but the availability may increase going forward as more custom checks are added to the Terraform module (contributions are welcome) and AWS Trusted Advisor.&lt;/li&gt;
&lt;li&gt;Compliance status and information for all mapped checks are collected and aggregated.
Resources in scope are fetched along with Tag Name, as available.
Contextual information and further guidance is retrieved.&lt;/li&gt;
&lt;li&gt;Scores and percentages are calculated.&lt;/li&gt;
&lt;li&gt;Data and information are grouped by pillar.&lt;/li&gt;
&lt;li&gt;The Executive Summary is generated. &lt;/li&gt;
&lt;li&gt;The report is produced based on Python Jinja2 templating functionality and uploaded to a dedicated bucket on Amazon S3. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This functionality is part of the Terraform module in the file &lt;a href="https://github.com/soprasteria/terraform-aws-wellarchitected-conformance/blob/main/wa_report_generator.tf" rel="noopener noreferrer"&gt;wa_report_generator.tf&lt;/a&gt; which includes the AWS Lambda function, a dedicated AWS KMS Key for encrypting the compliance reports (as resource and account information may be considered as sensitive) and a dedicated Amazon S3 bucket.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The solution now provides even more valuable insight to accelerate Well-Architected Framework Review conversations, providing more time to discuss opportunities for improvement. It fills some gaps if you don’t have AWS Premium Support available, and adds additional value otherwise. It can easily be deployed in existing Terraform pipelines. Please note that all resources will be cleaned up and deleted upon module call removal, including the reports, so make sure you remember &lt;a href="https://docs.aws.amazon.com/wellarchitected/latest/framework/rel-09.html" rel="noopener noreferrer"&gt;REL09&lt;/a&gt; and back up relevant reports accordingly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feedback and contributions
&lt;/h2&gt;

&lt;p&gt;If you have any feedback &lt;a href="https://hedrange.com/about" rel="noopener noreferrer"&gt;please let me know&lt;/a&gt; through your preferred medium of contact.&lt;/p&gt;

&lt;p&gt;If you would like to contribute with bugfixes, additional functionality or check coverage, &lt;a href="https://github.com/soprasteria/terraform-aws-wellarchitected-conformance/pulls" rel="noopener noreferrer"&gt;pull requests&lt;/a&gt; are welcome!&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/config/" rel="noopener noreferrer"&gt;AWS Config&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/config/latest/developerguide/conformance-packs.html" rel="noopener noreferrer"&gt;AWS Config Conformance Packs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/wellarchitected/latest/framework/welcome.html" rel="noopener noreferrer"&gt;AWS Well-Architected Framework&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/well-architected-tool/" rel="noopener noreferrer"&gt;AWS Well-Architected Tool&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/soprasteria/terraform-aws-wellarchitected-conformance/" rel="noopener noreferrer"&gt;GitHub: Terraform module terraform-aws-wellarchitected-conformance&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Development was accelerated by &lt;a href="https://aws.amazon.com/q/developer/" rel="noopener noreferrer"&gt;Amazon Q Developer&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://awslabs.github.io/mcp/servers/aws-diagram-mcp-server/" rel="noopener noreferrer"&gt;AWS Diagram MCP Server&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://awslabs.github.io/mcp/servers/aws-documentation-mcp-server/" rel="noopener noreferrer"&gt;AWS Documentation MCP Server&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The post &lt;a href="https://hedrange.com/2025/07/20/extensive-reporting-of-well-architected-maturity/" rel="noopener noreferrer"&gt;Extensive reporting of Well-Architected Maturity&lt;/a&gt; first appeared on &lt;a href="https://hedrange.com/" rel="noopener noreferrer"&gt;Håkon Eriksen Drange&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>articles</category>
      <category>aws</category>
      <category>wellarchitected</category>
    </item>
    <item>
      <title>How to measure Well-Architected maturity?</title>
      <dc:creator>Håkon Eriksen Drange</dc:creator>
      <pubDate>Tue, 15 Apr 2025 08:14:40 +0000</pubDate>
      <link>https://dev.to/haakoned/how-to-measure-well-architected-maturity-ikp</link>
      <guid>https://dev.to/haakoned/how-to-measure-well-architected-maturity-ikp</guid>
      <description>&lt;p&gt;&lt;strong&gt;TABLE OF CONTENTS&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;Challenge&lt;/li&gt;
&lt;li&gt;
Measuring Well-Architected maturity with Terraform and AWS Config

&lt;ul&gt;
&lt;li&gt;Functional flow of the solution&lt;/li&gt;
&lt;li&gt;Conceptual AWS architecture diagram&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

How to deploy and utilize

&lt;ul&gt;
&lt;li&gt;Viewing measurement insights in AWS Console&lt;/li&gt;
&lt;li&gt;Well-Architected Tool integration&lt;/li&gt;
&lt;li&gt;Event JSON examples for dry_run/live mode&lt;/li&gt;
&lt;li&gt;Event JSON for cleaning notes fields for all questions&lt;/li&gt;
&lt;li&gt;Notice about compliance checks and automation&lt;/li&gt;
&lt;li&gt;Cost of AWS Config evaluations&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;How to remove and decommission after use&lt;/li&gt;

&lt;li&gt;

Behind the scenes

&lt;ul&gt;
&lt;li&gt;AWS Config resources&lt;/li&gt;
&lt;li&gt;AWS Config Conformance Packs&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Feedback and contributions&lt;/li&gt;

&lt;li&gt;Resources&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=femopq3JWJg&amp;amp;t=5537" rel="noopener noreferrer"&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%2F2hogfr1n65z745flxjdr.png" width="800" height="364"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“I always think that you should be asking yourself:&lt;/em&gt;&lt;br&gt;&lt;br&gt;
&lt;em&gt;Are you Well-Architected?”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=femopq3JWJg&amp;amp;t=5537" rel="noopener noreferrer"&gt;Dr. Werner Vogels, AWS re:Invent keynote 2018&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Being Well-Architected means that you have taken care of the basics and your foundation is solid, so that you can move fast and focus on business requirements, with decreased risk of surprises. But how can we answer Werner’s question, with confidence? How can we &lt;em&gt;measure&lt;/em&gt; Well-Architected maturity in a tangible manner? And what is Well-Architected &lt;em&gt;enough&lt;/em&gt;, in your project phaze?&lt;/p&gt;

&lt;p&gt;As the first part of the journey I would suggest to &lt;a href="https://hedrange.com/2023/12/18/move-fast-and-avoid-surprises-be-well-architected/" rel="noopener noreferrer"&gt;plan and conduct a Well-Architected Framework Review&lt;/a&gt;; to have a conversation about your solution architecture and how the different best practices from AWS could apply in your context (Measure).&lt;/p&gt;

&lt;p&gt;In most review conversations I observe teams are spending a substantial amount of time trying to understand if the designed or provisioned resources are meeting the AWS recommended best practice configurations. “Did we set up alerting for this scenario?”, “Did we configure encryption at rest for the database cluster?”, “Did anyone get any alerts about spiking costs?”, “Is our documentation still up to date?” and so on.&lt;/p&gt;

&lt;p&gt;During a conversation, spending less efforts on &lt;em&gt;Measure&lt;/em&gt; could provide us with more time to &lt;em&gt;Learn&lt;/em&gt; about best practices and align on opportunities for &lt;em&gt;Improvement&lt;/em&gt;. Personally, I don’t believe that automation and AI capabilities will fully replace the WAFR lifecycle, but they may help &lt;em&gt;accelerate&lt;/em&gt; them, reducing Mean-Time To Deployed Improvement for your users (MTTDI) [yes, I just invented that term].&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%2F4ydvlnkdryn7v3vbu962.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%2F4ydvlnkdryn7v3vbu962.png" alt="AWS WAFR process" width="300" height="292"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure – Well-Architected Framework review cycle courtesy of AWS&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Challenge
&lt;/h2&gt;

&lt;p&gt;You could use a variety of options for measuring if cloud resources are meeting AWS best practices. Most commonly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://aws.amazon.com/security-hub/" rel="noopener noreferrer"&gt;AWS Security Hub&lt;/a&gt; with the &lt;a href="https://docs.aws.amazon.com/securityhub/latest/userguide/fsbp-standard.html" rel="noopener noreferrer"&gt;AWS Foundational Security Best Practices&lt;/a&gt; control reference (highly recommended).&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://aws.amazon.com/premiumsupport/technology/trusted-advisor/" rel="noopener noreferrer"&gt;AWS Trusted Advisor&lt;/a&gt; (full set of checks requires Business or Enterprise Support from AWS).&lt;/li&gt;
&lt;li&gt;3rd party open-source tools such as &lt;a href="https://prowler.com/" rel="noopener noreferrer"&gt;Prowler&lt;/a&gt; and &lt;a href="https://steampipe.io/" rel="noopener noreferrer"&gt;Steampipe&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;3rd party SaaS vendors offering similar functionality in APM/Observability services.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some of these options may not be available during a Well-Architected Framework Review due to company policies on changes potentially affecting the entire AWS Organization, cost development, security validations or procurement.&lt;/p&gt;

&lt;p&gt;But, what if AWS native technology, provisioned for a limited time period, would be acceptable? In this article I will share an possible approach to measure Well-Architected maturity in the form of AWS Config Conformance packs.&lt;/p&gt;
&lt;h2&gt;
  
  
  Measuring Well-Architected maturity with Terraform and AWS Config
&lt;/h2&gt;

&lt;p&gt;Recently I have been working on a Terraform module which can be utilized in scenarios with constraints as described above. A particular reflection I’ve made is that most tools focus primarily on security and reliability. Some dedicated offerings focus solely on Cloud Financial Management and Cost Optimization (also called FinOps), but finding one complete &lt;a href="https://en.wikipedia.org/wiki/Commercial_off-the-shelf" rel="noopener noreferrer"&gt;COTS&lt;/a&gt; Solution To Rule Them All (that doesn’t charge a premium) is unlikely.&lt;/p&gt;

&lt;p&gt;If we wrap up our sleeves and develop our own solution supporting our own custom logic we could also cover other aspects such as Cost Optimization.&lt;/p&gt;

&lt;p&gt;This Terraform module deploys AWS Config Conformance Packs mapped to pillars in the Well-Architected Framework.&lt;/p&gt;

&lt;p&gt;For relevant pillars in the &lt;a href="https://docs.aws.amazon.com/wellarchitected/latest/framework/welcome.html" rel="noopener noreferrer"&gt;AWS Well-Architected Framework&lt;/a&gt;, each best practice that is specific enough to be detected will report to be COMPLIANT or NON_COMPLIANT. Some best practices are harder to measure, or up to subjective consideration if a team is happy with how things are, or if the team considers there is room for improvement:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How a team evaluates culture and priorities.&lt;/li&gt;
&lt;li&gt;How satisfied a team is with insight into their workload(s) or business continuity and disaster recovery planning.&lt;/li&gt;
&lt;li&gt;How to practice cloud financial management.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Best practices in Operational Excellence are not straight forward to detect, as implementation of observability may have subjective opinion on room for improvement or may be performed with 3rd party tools. The main outcome of this module is to accelerate the Well-Architected Framework Review conversation, not to replace it with automation. Our hope is to shift the focus from “how did we configure this?” to “this is where we are today, what could we do to improve?”, thus freeing up valuable time for busy teams.&lt;/p&gt;

&lt;p&gt;In addition, the Notes field in the Well-Architected Tool can be populated directly with AWS Config resource compliance check results, leaving you with more insight to discuss improvement actions.&lt;/p&gt;

&lt;p&gt;| &lt;strong&gt;Well-Architected Framework Pillar&lt;/strong&gt; | &lt;strong&gt;Status as of April 2025&lt;/strong&gt; |&lt;br&gt;
| Operational Excellence | 0 checks |&lt;br&gt;
| Security (majority of checks) | 128 checks |&lt;br&gt;
| Reliabilit | 69 checks |&lt;br&gt;
| Performance Efficiency | 0 checks |&lt;br&gt;
| Cost Optimization | 6 checks |&lt;br&gt;
| Sustainability | 0 checks |&lt;/p&gt;
&lt;h4&gt;
  
  
  Functional flow of the solution
&lt;/h4&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%2F4y5xg9fve1di19twzlxv.jpg" 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%2F4y5xg9fve1di19twzlxv.jpg" width="500" height="1024"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure – Flow sequence accelerating WAFR conversations&lt;/em&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Conceptual AWS architecture diagram
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;AWS Config Configuration Recorder
• Records configuration changes for resources in your local AWS account (no impact or dependencies on AWS Organizations Config recorder(s))
• Set to record either daily or continuously (configurable)
• Stores configuration snapshots in a dedicated Amazon S3 bucket&lt;/li&gt;
&lt;li&gt;Amazon S3 Bucket
• Stores AWS Config configuration snapshots
• Stores CloudFormation templates for conformance packs
• Encrypted with a dedicated KMS key&lt;/li&gt;
&lt;li&gt;AWS Config Conformance Packs
• Well-Architected-Security
• Well-Architected-Reliability
• Well-Architected-Cost-Optimization
• Well-Architected-IAM (optional, subset of Security checks)&lt;/li&gt;
&lt;li&gt;Custom Lambda Functions
• Cost Optimization checks:
• Account structure implementation
• AWS Budgets configuration
• AWS Cost Anomaly Detection
• Organization information in cost and usage
• EC2 instances without Auto Scaling Groups&lt;/li&gt;
&lt;li&gt;Well-Architected Tool Updater Lambda function
• Retrieves compliance data from AWS Config
• Maps compliance results to specific Well-Architected Framework best practices
• Updates Notes fields in Well-Architected Tool&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%2F08ob11xxha4o3bhe4s5c.jpg" 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%2F08ob11xxha4o3bhe4s5c.jpg" width="800" height="722"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  How to deploy and utilize
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;At least two days before your planned review, deploy the module as suggested in &lt;a href="https://github.com/soprasteria/terraform-aws-wellarchitected-conformance/blob/main/examples/main.tf" rel="noopener noreferrer"&gt;examples/main.tf&lt;/a&gt; and described below. Compliance checks will update on a daily basis, to optimize costs for AWS Config Evaluations.&lt;/li&gt;
&lt;li&gt;Right before the review, trigger the Lambda function well_architected_tool_updater to update the Well-Architected Tool workload notes sections based on AWS Config Conformance packs compliance status.&lt;/li&gt;
&lt;li&gt;Run the review, look to the data in the notes field for discussion. No checked/answered questions will be modified, that would be up to subjective evaluation.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;provider "aws" {
  region = "eu-west-1" # Change to your preferred region
}

module "well_architected_conformance" {
  source = "git::https://github.com/soprasteria/terraform-aws-wellarchitected-conformance.git?ref=c006f439fc07d2e898cc7f67c5e7bcad1dcbd2e8"

  # AWS Config recording configuration
  recording_frequency = "DAILY" # Use DAILY to reduce costs

  # Deploy conformance packs
  deploy_security_conformance_pack = true
  deploy_reliability_conformance_pack = true
  deploy_cost_optimization_conformance_pack = true
  deploy_iam_conformance_pack = true
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Viewing measurement insights in AWS Console
&lt;/h3&gt;

&lt;p&gt;Navigating to AWS Config – Conformance packs will present a dashboard with packs for the Security, Reliability and Cost Optimization Pillars by default, plus IAM for Identity and Access Management, if enabled.&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%2Flopyc7a1lz1qn9avl4d0.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%2Flopyc7a1lz1qn9avl4d0.png" width="799" height="526"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can view the compliance score trend for each pillar/pack:&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%2Ffcyaii03gkk8g55xf2xw.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%2Ffcyaii03gkk8g55xf2xw.png" width="800" height="550"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also view the compliance status for each check, prefixed with the related best practice question, mapped to the &lt;a href="https://docs.aws.amazon.com/wellarchitected/latest/framework/the-pillars-of-the-framework.html" rel="noopener noreferrer"&gt;AWS Well-Architected Framework whitepaper&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%2F7l1ioleossacimi0nrec.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%2F7l1ioleossacimi0nrec.png" width="799" height="266"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Well-Architected Tool integration
&lt;/h3&gt;

&lt;p&gt;This module can also automatically update Well-Architected Tool workloads with compliance data from the AWS Config Conformance Packs.&lt;/p&gt;

&lt;p&gt;The Lambda function well_architected_tool_updater will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Process each conformance pack (Security, Reliability, Cost Optimization).&lt;/li&gt;
&lt;li&gt;Loop through all rules in sequence (SEC01, SEC02, REL01, REL02, COST01, etc.).&lt;/li&gt;
&lt;li&gt;For each rule, list the resource type, resource ID, and compliance status in the Notes field of the corresponding best practice question of your Well-Architected Tool workload.

&lt;ul&gt;
&lt;li&gt;The notes field has a limitation of maximum 2084 characters. When more resources are discovered than there is room for, resources will be summarized. &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Overwrite old data if triggered more than once. &lt;/li&gt;
&lt;li&gt;If you would like to erase all contents in all notes field, set the &lt;code&gt;clean_notes&lt;/code&gt; input parameter to 1.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The source code for the Lambda function is located in the &lt;a href="https://github.com/soprasteria/terraform-aws-wellarchitected-conformance/blob/main/src/wa_tool_updater" rel="noopener noreferrer"&gt;src/wa_tool_updater&lt;/a&gt; directory.&lt;/p&gt;

&lt;p&gt;To trigger the Well-Architected Tool updater, go to Well-Architected Tool and extract the Workload ID (not the full resource ARN).&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%2F6mbzc13p6keyrg9k9mxg.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%2F6mbzc13p6keyrg9k9mxg.png" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then go to AWS Lambda and find the function well_architected_tool_updater. Create test event JSON definition as follows (Console or CLI):&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%2Fl9uiow9a72x51iypi1x5.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%2Fl9uiow9a72x51iypi1x5.png" width="799" height="295"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Event JSON examples for dry_run/live mode
&lt;/h4&gt;

&lt;p&gt;Extract the Well-Architected Tool Workload ID from Properties – ARN. This example with &lt;code&gt;dry_run = 1&lt;/code&gt; will find relevant compliance data and log to CloudWatch Logs. No changes or updates will be performed.&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "workload_id": "141970ea95fd5b4329cea05202659f39",
  "dry_run": 1,
  "clean_notes": 0
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Flipping &lt;code&gt;dry_run = 0&lt;/code&gt; will perform updates of the notes field. No checked/answered questions will be modified.&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "workload_id": "141970ea95fd5b4329cea05202659f39",
  "dry_run": 0,
  "clean_notes": 0
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Event JSON for cleaning notes fields for all questions
&lt;/h4&gt;

&lt;p&gt;If you end up with a lot of mess and would like a fresh start, setting clean_notes to 1 will clean the notes field for all questions and return. No further changes to checked/answered questions or compliance data updates will be performed.&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "workload_id": "141970ea95fd5b4329cea05202659f39",
  "dry_run": 1,
  "clean_notes": 1
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected output is as follows. Full log output is available in Cloudwatch Logs.&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%2F33ox7fpmiomjgt36if5v.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%2F33ox7fpmiomjgt36if5v.png" width="800" height="511"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Back in Well-Architected Tool, the notes field will now be updated with detected compliance for &lt;em&gt;SEC 4. How do you detect and investigate security events?&lt;/em&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%2Ffsomoyc7bwbmvpi960x1.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%2Ffsomoyc7bwbmvpi960x1.png" width="796" height="654"&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%2Foapvlk59oczetg83aey2.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%2Foapvlk59oczetg83aey2.png" width="773" height="1024"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Notice about compliance checks and automation
&lt;/h4&gt;

&lt;p&gt;Check data is based on all resources in the current AWS account. Tagging based filtering is currently not supported. Be aware if you have multiple workloads in the same AWS account.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cost of AWS Config evaluations
&lt;/h3&gt;

&lt;p&gt;According to the &lt;a href="https://aws.amazon.com/config/pricing/" rel="noopener noreferrer"&gt;AWS Config pricing page&lt;/a&gt;; &lt;em&gt;“With AWS Config, you are charged based on the number of configuration items recorded, the number of active AWS Config rule evaluations, and the number of conformance pack evaluations in your account. A configuration item is a record of the configuration state of a resource in your AWS account. An AWS Config rule evaluation is a compliance state evaluation of a resource by an AWS Config rule in your AWS account. A conformance pack evaluation is the evaluation of a resource by an AWS Config rule within the conformance pack”.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;AWS Config supports &lt;a href="https://docs.aws.amazon.com/config/latest/developerguide/select-resources.html#select-resources-recording-frequency" rel="noopener noreferrer"&gt;Continuous recording and Daily recording&lt;/a&gt;. You can choose between Daily or Continuous by setting the desired value for the variable &lt;code&gt;recording_frequency&lt;/code&gt;, which defaults to DAILY.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to remove and decommission after use
&lt;/h2&gt;

&lt;p&gt;Some might see this solution as valuable long-term, others might have other tools coming in which overlaps.&lt;/p&gt;

&lt;p&gt;As this Terraform module deploys an S3 bucket for storing Config evaluations, the bucket must be emptied before it can be deleted.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Empty the bucket in the AWS Console.&lt;/li&gt;
&lt;li&gt;Remove the Terraform module call declaration from your code base.&lt;/li&gt;
&lt;li&gt;Trigger your CI/CD pipeline.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Behind the scenes
&lt;/h2&gt;

&lt;p&gt;Some Terraform snippets on how to deploy an AWS Config Conformance Pack&lt;/p&gt;

&lt;p&gt;How to write a custom AWS Config check.&lt;/p&gt;

&lt;h4&gt;
  
  
  AWS Config resources
&lt;/h4&gt;

&lt;p&gt;To avoid dependencies or conflicts with existing AWS Organization based AWS Config, this module deploys a dedicated AWS Config Recorder, which has to be started after provisioning.&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Excerpts for illustration, not complete example, see main.tf

# AWS Config Delivery Channel to S3
resource "aws_config_delivery_channel" "well_architected" {
  name = "well_architected_config_delivery_channel"
  s3_bucket_name = module.aws_config_well_architected_recorder_s3_bucket.s3_bucket_id
  depends_on = [aws_config_configuration_recorder.well_architected]
}

# AWS Config Configuration Recorder with recording_frequency set by input variable
resource "aws_config_configuration_recorder" "well_architected" {
  name = "well-architected"
  role_arn = aws_iam_role.config_role.arn

  recording_group {
    all_supported = true
    include_global_resource_types = true
  }

  recording_mode {
    recording_frequency = var.recording_frequency
  }
}

# AWS Config retention configuration: Number of days AWS Config stores your historical information.
resource "aws_config_retention_configuration" "example" {
  retention_period_in_days = 400
}

# Manages status (recording / stopped) of an AWS Config Configuration Recorder.
resource "aws_config_configuration_recorder_status" "well_architected" {
  name = aws_config_configuration_recorder.well_architected.name
  is_enabled = true
  depends_on = [aws_config_delivery_channel.well_architected]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  AWS Config Conformance Packs
&lt;/h4&gt;

&lt;p&gt;Security, Reliability and IAM conformance packs are based on AWS’ library of &lt;a href="https://docs.aws.amazon.com/config/latest/developerguide/conformancepack-sample-templates.html" rel="noopener noreferrer"&gt;Conformance Pack Sample Templates for AWS Config&lt;/a&gt; (in Cloudformation format):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/config/latest/developerguide/operational-best-practices-for-wa-Security-Pillar.html" rel="noopener noreferrer"&gt;Operational Best Practices for AWS Well-Architected Framework Security Pillar&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/config/latest/developerguide/operational-best-practices-for-wa-Reliability-Pillar.html" rel="noopener noreferrer"&gt;Operational Best Practices for AWS Well-Architected Framework Reliability Pillar&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/config/latest/developerguide/operational-best-practices-for-aws-identity-and-access-management.html" rel="noopener noreferrer"&gt;Operational Best Practices for AWS Identity And Access Management&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The underlying checks are &lt;a href="https://docs.aws.amazon.com/config/latest/developerguide/managed-rules-by-aws-config.html" rel="noopener noreferrer"&gt;AWS Config Managed Rules&lt;/a&gt; and cannot be edited. The Cloudformation templates are imported in Terraform as data objects. ConfigRuleNames are replaced to suit the particular Well-Architected Framework Pillar and best practice.&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Excerpts for illustration, not complete example
locals {
  url_template_body_wa_security_pillar = "https://raw.githubusercontent.com/awslabs/aws-config-rules/refs/heads/master/aws-config-conformance-packs/Operational-Best-Practices-for-AWS-Well-Architected-Security-Pillar.yaml"
}

data "http" "template_body_wa_security_pillar" {
  url = local.url_template_body_wa_security_pillar
}

data "util_replace" "transformed_wa_security_pillar" {
  content = data.http.template_body_wa_security_pillar.response_body
  replacements = {
    "account-part-of-organizations" : "SEC01-securely-operate_bp_account-part-of-organizations",
    "ec2-instance-managed-by-systems-manager" : "SEC01-securely-operate_bp_ec2-instance-managed-by-systems-manager",
    "codebuild-project-envvar-awscred-check" : "SEC01-securely-operate_bp_codebuild-project-envvar-awscred-check",
    "mfa-enabled-for-iam-console-access" : "SEC02-identities_bp_mfa-enabled-for-iam-console-access"
     # .. and so on
    }
}

# Render templates to file on S3 to avoid template_body file limitation of 51,200 bytes
resource "aws_s3_object" "cloudformation_wa_config_security_template" {
  bucket = module.aws_config_well_architected_recorder_s3_bucket.s3_bucket_id
  key = "Cloudformation/wa-config-security.yaml"
  content = data.util_replace.transformed_wa_security_pillar.replaced
  content_type = "application/yaml"
}

# Takes the source Cloudformation file from S3, generates an AWS Config Conformance pack which behind the scenes creates an AWS managed Cloudformation stack. 
resource "aws_config_conformance_pack" "well_architected_conformance_pack_security" {
  count = var.deploy_security_conformance_pack ? 1 : 0
  name = "Well-Architected-Security"
  template_s3_uri = "s3://${module.aws_config_well_architected_recorder_s3_bucket.s3_bucket_id}/${aws_s3_object.cloudformation_wa_config_security_template.key}"
  depends_on = [aws_config_configuration_recorder.well_architected]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Cost Optimization Conformance Pack is built from scratch. &lt;a href="https://docs.aws.amazon.com/config/latest/developerguide/evaluate-config_develop-rules.html" rel="noopener noreferrer"&gt;Custom Lambda Rules&lt;/a&gt; may be implemented like this:&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# AWS Lambda function based on module from terraform-aws-modules
module "lambda_function_wa_conformance_cost_03_aws_budgets" {
  source = "git::https://github.com/terraform-aws-modules/terraform-aws-lambda.git?ref=f7866811bc1429ce224bf6a35448cb44aa5155e7"
  trigger_on_package_timestamp = false
  function_name = "WA-COST03-BP05-AWS-Budgets"
  description = "AWS Config Custom Rule which checks for AWS Budgets setup according to WAF COST03-BP05."
  handler = "index.lambda_handler"
  runtime = var.lambda_python_runtime
  source_path = "../../local-modules/wa-config-conformance/src/cost03_aws_budgets/index.py"
  attach_policy_statements = true
  timeout = var.lambda_timeout
  cloudwatch_logs_retention_in_days = var.lambda_cloudwatch_logs_retention_in_days
  policy_statements = {
    statement = {
      effect = "Allow"
      actions = [
        "budgets:DescribeBudgets",
        "budgets:ViewBudget",
        "config:PutEvaluations"
      ]
      resources = ["*"]
    }
  }

  tags = {
    Name = "Well-Architected-Conformance-COST03-BP05-AWS-Budgets"
  }
}

resource "aws_config_config_rule" "cost_01_aws_budgets" {
  name = "cost01-cloud-financial-management_bp_aws-budgets"
  description = "Checks for AWS Budgets setup according to WAF COST01-BP05 Report and notify on cost optimization."

  source {
    owner = "CUSTOM_LAMBDA"
    source_identifier = module.lambda_function_wa_conformance_cost_03_aws_budgets.lambda_function_arn

    source_detail {
      message_type = "ScheduledNotification"
      maximum_execution_frequency = var.scheduled_config_custom_lambda_periodic_trigger_interval
    }
  }

  depends_on = [module.lambda_function_wa_conformance_cost_03_aws_budgets]
}

# Lambda permissions for all AWS Config Custom Lambda Rules
resource "aws_lambda_permission" "config_permissions" {
  for_each = toset([
    module.lambda_function_wa_conformance_cost_02_account_structure_implemented.lambda_function_name,
    module.lambda_function_wa_conformance_cost_03_aws_budgets.lambda_function_name,
    module.lambda_function_wa_conformance_cost_03_aws_cost_anomaly_detection.lambda_function_name,
    module.lambda_function_wa_conformance_cost_03_add_organization_information_to_cost_and_usage.lambda_function_name,
    module.lambda_function_wa_conformance_cost_04_ec2_instances_without_auto_scaling.lambda_function_name
  ])

  statement_id = "AllowConfigInvoke"
  action = "lambda:InvokeFunction"
  function_name = each.value
  principal = "config.amazonaws.com"
  source_account = local.aws_account_id
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Feedback and contributions
&lt;/h2&gt;

&lt;p&gt;If you have any feedback, &lt;a href="https://dev.to/about"&gt;please let me know&lt;/a&gt; through your preferred medium of contact.&lt;/p&gt;

&lt;p&gt;If you would like to contribute with additional functionality and check coverage, &lt;a href="https://github.com/soprasteria/terraform-aws-wellarchitected-conformance/pulls" rel="noopener noreferrer"&gt;pull requests&lt;/a&gt; are welcome!&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/config/" rel="noopener noreferrer"&gt;AWS Config&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/config/latest/developerguide/conformance-packs.html" rel="noopener noreferrer"&gt;AWS Config Conformance Packs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/wellarchitected/latest/framework/welcome.html" rel="noopener noreferrer"&gt;AWS Well-Architected Framework&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/well-architected-tool/" rel="noopener noreferrer"&gt;AWS Well-Architected Tool&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/soprasteria/terraform-aws-wellarchitected-conformance/" rel="noopener noreferrer"&gt;GitHub: Terraform module terraform-aws-wellarchitected-conformance&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The post &lt;a href="https://hedrange.com/2025/04/15/how-to-measure-well-architected-maturity/" rel="noopener noreferrer"&gt;How to measure Well-Architected maturity?&lt;/a&gt; first appeared on &lt;a href="https://hedrange.com/" rel="noopener noreferrer"&gt;Håkon Eriksen Drange&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>articles</category>
      <category>aws</category>
      <category>wellarchitected</category>
      <category>measuring</category>
    </item>
    <item>
      <title>AWS re:Invent re:Cap talk – Simplifying developer experience with new features in AWS Step Functions</title>
      <dc:creator>Håkon Eriksen Drange</dc:creator>
      <pubDate>Fri, 17 Jan 2025 07:43:58 +0000</pubDate>
      <link>https://dev.to/aws-builders/aws-reinvent-recap-talk-simplifying-developer-experience-with-new-features-in-aws-step-functions-5042</link>
      <guid>https://dev.to/aws-builders/aws-reinvent-recap-talk-simplifying-developer-experience-with-new-features-in-aws-step-functions-5042</guid>
      <description>&lt;p&gt;Monday January 13th I was invited by AWS User Group Oslo to participate in the traditional &lt;a href="https://www.meetup.com/AWS-User-Group-Norway/events/305162785" rel="noopener noreferrer"&gt;AWS re:Invent re:Cap meetup&lt;/a&gt;. My re:Cap contribution was a reflection on recent and valuable features in AWS Step Functions which can make the developer experience simpler and more efficient, reducing the time from idea to business value.&lt;/p&gt;

&lt;h4&gt;
  
  
  Main topics of my re:Cap talk
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;How to manage AWS Step Functions configuration with Infrastructure-as-Code&lt;/li&gt;
&lt;li&gt;Replacing application (Lambda) code with Step Functions workflow configuration&lt;/li&gt;
&lt;li&gt;Breaking apart a “Lambda-lith”&lt;/li&gt;
&lt;li&gt;Step Functions intrinsic functions&lt;/li&gt;
&lt;li&gt;New support for JSONata, in addition to JSONPath&lt;/li&gt;
&lt;li&gt;Walkthrough of JSONata native functionality&lt;/li&gt;
&lt;li&gt;New support for Variables&lt;/li&gt;
&lt;li&gt;Step Functions Distributed Map state&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;“&lt;em&gt;Customers choose Step Functions to build complex workflows that involve multiple services such as &lt;a href="https://aws.amazon.com/lambda/" rel="noopener noreferrer"&gt;AWS Lambda&lt;/a&gt;, &lt;a href="https://aws.amazon.com/fargate/" rel="noopener noreferrer"&gt;AWS Fargate&lt;/a&gt;, &lt;a href="https://aws.amazon.com/bedrock/" rel="noopener noreferrer"&gt;Amazon Bedrock&lt;/a&gt;, and HTTP API integrations. Within these workflows, you build states to interface with these various services, passing input data and receiving responses as output. While you can use Lambda functions for date, time, and number manipulations beyond Step Functions’ intrinsic capabilities, these methods struggle with increasing complexity, leading to payload restrictions, data conversion burdens, and more state changes. This affects the overall cost of the solution. You use variables and JSONata to address this.&lt;/em&gt;“&lt;/p&gt;

&lt;p&gt;&lt;cite&gt;&lt;a href="https://aws.amazon.com/blogs/compute/simplifying-developer-experience-with-variables-and-jsonata-in-aws-step-functions/" title="" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;a href="https://aws.amazon.com/blogs/compute/simplifying-developer-experience-with-variables-and-jsonata-in-aws-step-functions/" title="" rel="noopener noreferrer"&gt;AWS Compute Blog: Simplifying developer experience with variables and JSONata in AWS Step Functions&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Source material references this re:Cap talk was based upon are listed below.&lt;/p&gt;

&lt;h4&gt;
  
  
  Event highlights
&lt;/h4&gt;

&lt;p&gt;Gunnar Grosch from AWS kicked off the event with highlights of new announcements from the pre:Invent and actual re:Invent period with a deeper dive on &lt;a href="https://aws.amazon.com/blogs/database/introducing-amazon-aurora-dsql/" rel="noopener noreferrer"&gt;Amazon Aurora DSQL&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%2Fuyawyru9jbyhfyzqqt6r.jpg" 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%2Fuyawyru9jbyhfyzqqt6r.jpg" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then it was my turn before Colin from Capra Consulting shared his perception about the state of Platform Engineering.&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%2Fmah1322o81olb24ceint.jpg" 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%2Fmah1322o81olb24ceint.jpg" width="800" height="600"&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%2Fyed3rg84kb7wml1m622a.jpg" 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%2Fyed3rg84kb7wml1m622a.jpg" width="800" height="503"&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%2F42w5a672cds293h1e6qa.jpg" 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%2F42w5a672cds293h1e6qa.jpg" width="800" height="600"&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%2Fqjzcodsyp8ktujb9m84i.jpg" 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%2Fqjzcodsyp8ktujb9m84i.jpg" width="800" height="600"&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%2Fymxta1781lx6i71qbms4.jpg" 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%2Fymxta1781lx6i71qbms4.jpg" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The meetup wrapped up with an interesting panel discussion with Martin from Sopra Steria, Gunnar from AWS, Anders from Webstep and Erlend from Capra.&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%2Fvj27lawortuaxprztqnw.jpg" 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%2Fvj27lawortuaxprztqnw.jpg" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Many thanks to the organizers for a great event.&lt;/p&gt;

&lt;h4&gt;
  
  
  Slides from my re:Cap talk
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://hedrange.com/wp-content/uploads/2025/01/2025-01-13_-_aws_user_group_oslo_reinvent_recap_-_simplifying_developer_experience_with_new_features_in_aws_step_functions_hed.pdf" rel="noopener noreferrer"&gt;Simplifying developer experience with new features in AWS Step Functions&lt;/a&gt;&lt;a href="https://hedrange.com/wp-content/uploads/2025/01/2025-01-13_-_aws_user_group_oslo_reinvent_recap_-_simplifying_developer_experience_with_new_features_in_aws_step_functions_hed.pdf" rel="noopener noreferrer"&gt;Download&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Reference material
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/step-functions/" rel="noopener noreferrer"&gt;AWS Step Functions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/blogs/compute/simplifying-developer-experience-with-variables-and-jsonata-in-aws-step-functions/" rel="noopener noreferrer"&gt;AWS Compute Blog: Simplifying developer experience with variables and JSONata in AWS Step Functions/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://serverlessland.com/reinvent2024/api402" rel="noopener noreferrer"&gt;https://serverlessland.com/reinvent2024/api402&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://serverlessland.com/reinvent2024/svs401" rel="noopener noreferrer"&gt;https://serverlessland.com/reinvent2024/svs401&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/playlist?list=PL2yQDdvlhXf_Ezjnq7A7LfHBgCYSqzrZS" rel="noopener noreferrer"&gt;https://www.youtube.com/playlist?list=PL2yQDdvlhXf_Ezjnq7A7LfHBgCYSqzrZS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://community.aws/recaps" rel="noopener noreferrer"&gt;AWS Community re:Caps incl. full deck download&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The post &lt;a href="https://hedrange.com/2025/01/17/aws-reinvent-recap-talk-simplifying-developer-experience-with-new-features-in-aws-step-functions/" rel="noopener noreferrer"&gt;AWS re:Invent re:Cap talk – Simplifying developer experience with new features in AWS Step Functions&lt;/a&gt; first appeared on &lt;a href="https://hedrange.com" rel="noopener noreferrer"&gt;Håkon Eriksen Drange&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>articles</category>
      <category>aws</category>
      <category>stepfunctions</category>
    </item>
    <item>
      <title>Increase system reliability with Immutable Infrastructure – Move fast and avoid surprises</title>
      <dc:creator>Håkon Eriksen Drange</dc:creator>
      <pubDate>Fri, 09 Aug 2024 17:52:53 +0000</pubDate>
      <link>https://dev.to/haakoned/increase-system-reliability-with-immutable-infrastructure-move-fast-and-avoid-surprises-130n</link>
      <guid>https://dev.to/haakoned/increase-system-reliability-with-immutable-infrastructure-move-fast-and-avoid-surprises-130n</guid>
      <description>&lt;p&gt;&lt;strong&gt;Table of contents&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The largest outage in history of IT (so far)&lt;/li&gt;
&lt;li&gt;Configuration drift&lt;/li&gt;
&lt;li&gt;The concept of Immutable Infrastructure&lt;/li&gt;
&lt;li&gt;
Immutable Infrastructure in the AWS Cloud

&lt;ul&gt;
&lt;li&gt;Virtual machine based workloads on EC2&lt;/li&gt;
&lt;li&gt;Container based workloads&lt;/li&gt;
&lt;li&gt;Immutability for Docker and local testing&lt;/li&gt;
&lt;li&gt;Immutability for workloads provisioned with AWS Elastic Container Service (ECS based on EC2 or Fargate)&lt;/li&gt;
&lt;li&gt;Immutability for workloads provisioned with AWS Elastic Kubernetes Service&lt;/li&gt;
&lt;li&gt;About Docker labels/image tags and deployment&lt;/li&gt;
&lt;li&gt;Immutability for serverless workloads provisioned with AWS Lambda&lt;/li&gt;
&lt;li&gt;Data handling in the immutable infrastructure model&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Conclusion&lt;/li&gt;

&lt;li&gt;References&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  The largest outage in history of IT (so far)
&lt;/h2&gt;

&lt;p&gt;On 19 July 2024, the cybersecurity company CrowdStrike distributed a faulty update to its Falcon Sensor security software that caused widespread problems with Microsoft Windows computers running the software. As a result, roughly 8.5 million systems crashed and were unable to properly restart in what has been called the &lt;a href="https://www.cnbc.com/2024/07/19/latest-live-updates-on-a-major-it-outage-spreading-worldwide.html" rel="noopener noreferrer"&gt;largest outage in the history&lt;/a&gt; of information technology and “historic in scale”.&lt;/p&gt;

&lt;p&gt;The outage disrupted daily life, businesses, and governments around the world. Many industries were affected; airlines, airports, banks, hotels, hospitals, manufacturing, stock markets, broadcasting, gas stations, retail stores, emergency services and governmental websites. The worldwide financial damage has been estimated to be at least $10 billion.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;What happened? *&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The CrowdStrike Falcon software suite consists, highly simplified, of an software application (agent), which is versioned, and Rapid Response Content configuration updates, the latter not being versioned, to facilitate rapid deployment. However, the outcome of this change was not quality assured properly before widespread deployment. In their &lt;a href="https://www.crowdstrike.com/falcon-content-update-remediation-and-guidance-hub/" rel="noopener noreferrer"&gt;Preliminary Post Incident Review&lt;/a&gt;, CrowdStrike indicated they promised to improve their quality assurance process and provide customers with “greater control over the delivery of Rapid Response Content updates by allowing granular selection of when and where these updates are deployed.”&lt;/p&gt;

&lt;p&gt;I would like to highlight that there are certain nuances to this case (rapid distribution of security detection/mitigation mechanisms, client machines vs. servers etc.), but my personal stance is that any system that is providing mission critical services should have a level of quality assurance that matches it’s Service Level Agreement/Objective and risk tolerance.&lt;/p&gt;

&lt;p&gt;Outages like this are not something new. It can happen to any system where changes are applied live or in-place, without being tested properly in advance. Service disruptions can also happen during regular operating system patching procedures, if/when a bug or a security issue is introduced, or with any application or server process, depending on how privileged the process is. &lt;/p&gt;

&lt;h2&gt;
  
  
  Configuration drift
&lt;/h2&gt;

&lt;p&gt;Long running systems will over the course of time experience configuration drift. A clean server started from a golden image immediately has a high degree of certainty of it’s current state, but as time goes by, certainty decreases. In a traditional (legacy) deployment model, software packages are updated, cache and log files are stored on disk. Perhaps not all log file locations are configured for rotation, perhaps an application stores temp files at a non-standard location and not all temp files are cleaned up. Over time disk space utilization increases, and at some point in the critical partitions, such as /boot may become full without it being noticed. The server’s state has now deviated significantly from its initial state, and it may be hard to reproduce that same state. &lt;/p&gt;

&lt;p&gt;A software update that passed QA in staging is not guaranteed be successfully applied in production because of lacking disk space or some trash that has been lying around, accumulating over time, or custom, manual configurations which was supposed to “improve” something in the live environment. &lt;/p&gt;

&lt;p&gt;Let’s say you start comparing a green Granny Smith against a green Granny Smith, but over the course of time the green Granny Smith may have evolved to a Red Delicious apple, and perhaps to an orange Asian pear!&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%2F61f1ux2mj6kr6jdbbqez.jpg" 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%2F61f1ux2mj6kr6jdbbqez.jpg" width="739" height="1352"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For production systems, especially mission-critical ones, risk management becomes increasingly important. An operating model based on long running servers is prone to introduce risk and uncertainty as time goes by. Staging and production environments can over the course of time drift and not be identical anymore. You end up with a &lt;a href="https://martinfowler.com/bliki/SnowflakeServer.html" rel="noopener noreferrer"&gt;Snowflake Server&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Can the new state be predicted? Can the previously known state be reproduced? If not, you may have a challenge with rollbacks and disaster recovery.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;It is a good idea to virtually burn down your servers at regular intervals. A server should be like a phoenix, regularly rising from the ashes.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;cite&gt;&lt;a href="https://martinfowler.com/bliki/PhoenixServer.html" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;a href="https://martinfowler.com/bliki/PhoenixServer.html" rel="noopener noreferrer"&gt;https://martinfowler.com/bliki/PhoenixServer.html&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The concept of Immutable Infrastructure
&lt;/h2&gt;

&lt;p&gt;The Cambridge dictionary definition of Immutability is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;The state of not changing, or being unable to be changed&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;cite&gt;&lt;a href="https://dictionary.cambridge.org/dictionary/english/immutability" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;a href="https://dictionary.cambridge.org/dictionary/english/immutability" rel="noopener noreferrer"&gt;https://dictionary.cambridge.org/dictionary/english/immutability&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With the advent of cloud and more capabilities for automation at our disposal, an alternative pattern called Immutable Infrastructure has gained popularity. The concept is simply about starting from a clean, well-known slate, every time a change is performed.&lt;/p&gt;

&lt;p&gt;The concept of the Immutable Server, or disposable servers, was introduced around 2012 by actors such as Thoughtworks, Netflix and Google. Instead of using configuration management to try to keep systems in compliance, they advocated for using configuration management to create base images for servers that could be torn down and rebuilt at will.&lt;/p&gt;

&lt;p&gt;An Immutable Server is the logical conclusion of this approach, a server that once deployed, is never modified or changed. No software or operating system updates, security patches, application releases or configuration changes are being performed in-place on live production servers. “&lt;a href="https://cloudscaling.com/blog/cloud-computing/the-history-of-pets-vs-cattle/" rel="noopener noreferrer"&gt;Treat your servers like cattle, not like pets&lt;/a&gt;” gained traction. If there was a problem with a live server, it would be terminated and replaced by a new one, from a known state.&lt;/p&gt;

&lt;p&gt;Not even new application releases/software artifacts, are deployed to existing servers. The running servers are replaced with new instances that has software artifacts built-in. With load balancing, automatic health checks and blue/green or canary capabilities, deploying new servers or rolling back to previous versions can be done without end user impact (if done correctly).&lt;/p&gt;

&lt;p&gt;This concept is also described in the AWS Well-Architected Framework – Reliability Pillar – &lt;a href="https://docs.aws.amazon.com/wellarchitected/latest/framework/rel_tracking_change_management_immutable_infrastructure.html" rel="noopener noreferrer"&gt;REL08-BP04 Deploy using immutable infrastructure&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Implementing in-place changes to running infrastructure resources&lt;/em&gt;, the common approach, is actually stated as an anti-pattern.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Benefits of establishing this best practice:&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;_ &lt;strong&gt;Increased consistency across environments:&lt;/strong&gt;  Since there are no differences in infrastructure resources across environments, consistency is increased and testing is simplified._&lt;/li&gt;
&lt;li&gt;_ &lt;strong&gt;Reduction in configuration drifts:&lt;/strong&gt;  By replacing infrastructure resources with a known and version-controlled configuration, the infrastructure is set to a known, tested, and trusted state, avoiding configuration drifts._&lt;/li&gt;
&lt;li&gt;_ &lt;strong&gt;Reliable atomic deployments:&lt;/strong&gt;  Deployments either complete successfully or nothing changes, increasing consistency and reliability in the deployment process._&lt;/li&gt;
&lt;li&gt;_ &lt;strong&gt;Simplified deployments:&lt;/strong&gt;  Deployments are simplified because they don’t need to support upgrades. Upgrades are just new deployments._&lt;/li&gt;
&lt;li&gt;_ &lt;strong&gt;Safer deployments with fast rollback and recovery processes:&lt;/strong&gt;  Deployments are safer because the previous working version is not changed. You can roll back to it if errors are detected._&lt;/li&gt;
&lt;li&gt;_ &lt;strong&gt;Enhanced security posture:&lt;/strong&gt;  By not allowing changes to infrastructure, remote access mechanisms (such as SSH) can be disabled. This reduces the attack vector, improving your organization’s security posture._&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Immutable Infrastructure in the AWS Cloud
&lt;/h2&gt;

&lt;p&gt;This practice can be achieved regardless of compute option, but to reduce time to market and operational overhead it mandates a high degree of automation with CI/CD tools such as AWS CodePipeline and GitHub Actions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Virtual machine based workloads on EC2
&lt;/h3&gt;

&lt;p&gt;In this scenario an automated routine is established which produces virtual machine images called EC2 Amazon Machine Images (AMI). This “golden image” includes the respective version of application source code and it’s dependencies such as operating system services, at the expected versions. AMIs can be produced without environment specific configuration built-in, to be fetched from &lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html" rel="noopener noreferrer"&gt;AWS Systems Manager Parameter Store&lt;/a&gt; and/or &lt;a href="https://docs.aws.amazon.com/secretsmanager/latest/userguide/intro.html" rel="noopener noreferrer"&gt;AWS Secrets Manager&lt;/a&gt;, so that the same image can be deployed and verified in dev/test, staging and production environments.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.packer.io/" rel="noopener noreferrer"&gt;Hashicorp Packer&lt;/a&gt; has been available for quite some time. AWS also provides the native service &lt;a href="https://aws.amazon.com/image-builder/" rel="noopener noreferrer"&gt;EC2 Image Builder&lt;/a&gt; for this purpose.&lt;/p&gt;

&lt;p&gt;Amazon provides managed AMIs for both Linux (&lt;a href="https://docs.aws.amazon.com/linux/al2023/ug/ec2.html" rel="noopener noreferrer"&gt;Amazon Linux&lt;/a&gt; based on Fedora) and &lt;a href="https://docs.aws.amazon.com/ec2/latest/windows-ami-reference/windows-ami-versions.html" rel="noopener noreferrer"&gt;Windows&lt;/a&gt; workloads that are tailored for security and performance in AWS.&lt;/p&gt;

&lt;p&gt;With Immutable Infrastructure, Windows Updates and Linux unattended-upgrades are disabled. Every time AWS releases a new officially supported base AMI version, or ad-hoc updates are required, the EC2 Image Builder Pipeline is triggered, which produces a new artifact and validates each change. Every new application version fetches the latest quality assured base AMI and produces a self-contained application AMI.&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%2F7fapx499c9q77eaelmbd.jpg" 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%2F7fapx499c9q77eaelmbd.jpg" width="422" height="1024"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can find a Terraform example with inline comments and explanations of EC2 Image Builder resources below .&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# EC2 Image Builder component that installs Git and Nginx, clones a sample app repo from GitHub and starts the Nginx web server
resource "aws_imagebuilder_component" "hello_world" {
  name = "hello-world-component"
  platform = "Linux"
  version = "1.0.0"
  description = "Hello World application component"

  data {
    name = "hello-world-app-script"
    type = "AWS_LAMBDA"
    content = &amp;lt;&amp;lt;-EOF
      # Install Git and Nginx
      yum install -y git nginx

      # Clone sample Hello World app from GitHub
      mkdir app-repo
      git clone https://github.com/aws-samples/aws-codepipeline-s3-codedeploy-linux app-repo

      # Copy cloned files to Nginx public HTML directory
      cp -r app-repo/* /usr/share/nginx/html/

      # Start Nginx
      systemctl start nginx
    EOF
  }
}

# Recipe named nginx-recipe which includes the nginx-component.
# parent_image is set to Amazon Linux 2 AMI version 2024.7.20. This can be parameterized. 
resource "aws_imagebuilder_image_recipe" "nginx_recipe" {
  name = "nginx-recipe"
  parent_image = "arn:aws:imagebuilder:${var.region}:aws:image/amazon-linux-2-x86/2024.7.20"
  version = "1.0.0"

  component {
    component_arn = aws_imagebuilder_component.nginx.arn
  }
}

# Defines an infrastructure configuration, including the instance profile, security group, and subnet (intentionally not included)
resource "aws_imagebuilder_infrastructure_configuration" "nginx_infra" {
  name = "nginx-infra"
  instance_profile_name = aws_iam_instance_profile.image_builder_instance_profile.name
  security_group_ids = [aws_security_group.image_builder_sg.id]
  subnet_id = aws_subnet.image_builder_subnet.id
  terminate_instance_on_failure = true
}

# Pipeline output should be an Amazon Machine Image (AMI) with a name based on the build date.
resource "aws_imagebuilder_distribution_configuration" "nginx_distribution" {
  name = "nginx-distribution"

  distribution {
    ami_distribution_configuration {
      name = "nginx-ami-{{ imagebuilder:buildDate }}"
    }
    region = var.region
  }
}

# Defines the Image Builder pipeline, ties together the recipe, infrastructure configuration, and distribution configuration.
resource "aws_imagebuilder_image_pipeline" "nginx_pipeline" {
  name = "nginx-pipeline"
  image_recipe_arn = aws_imagebuilder_image_recipe.nginx_recipe.arn
  infrastructure_configuration_arn = aws_imagebuilder_infrastructure_configuration.nginx_infra.arn
  distribution_configuration_arn = aws_imagebuilder_distribution_configuration.nginx_distribution.arn
}

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Container based workloads
&lt;/h3&gt;

&lt;p&gt;In your CI/CD tool of choice, container images are produced and pushed to a container repository such as AWS Elastic Container Registry (ECR). Even though immutability is one of the foundations behind containers, it is not restricted, but based on how the container configuration is specified and launched. Container root filesystems are usually writable by default.&lt;/p&gt;

&lt;p&gt;In many cases software packages are being updated on container launch, but this is an anti-pattern in immutability. Update installed packages and install Apache could yield different results if executed at different points in time, as new upstream software package updates are made available. We need to ensure that we test the exact same artifact in staging that is being deployed to production, so the recommended approach is to update all packages at container build time and then launch it with the root storage partition in read only mode.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;The container’s root filesystem should be treated as a ‘golden image’ by using Docker run’s &lt;code&gt;--read-only&lt;/code&gt; option. This prevents any writes to the container’s root filesystem at container runtime and enforces the principle of immutable infrastructure.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;cite&gt;&lt;a href="https://docs.datadoghq.com/security/default_rules/cis-docker-1.2.0-5.12/" title="" rel="noopener noreferrer"&gt;&lt;em&gt;CIS Docker Benchmark control 5.12 – Datadog&lt;/em&gt;&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Immutability for Docker and local testing
&lt;/h4&gt;

&lt;p&gt;Adding a read-only flag at the container’s runtime enforces the container’s root filesystem being mounted as read only. With the –tmpfs option it is possible to mount a temporary file system for non-persistent data/cache.&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run &amp;lt;Run arguments&amp;gt; --read-only &amp;lt;Container Image Name or ID&amp;gt; &amp;lt;Command&amp;gt;

# Example with the --tmpfs option to mount a temporary file system for non-persistent data/cache
docker run --interactive --tty --read-only --tmpfs "/run" --tmpfs "/tmp" ubuntu /bin/bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Immutability for workloads provisioned with AWS Elastic Container Service (ECS based on EC2 or Fargate)
&lt;/h4&gt;

&lt;p&gt;Configure the &lt;a href="https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_definition_parameters.html#container_definitions" rel="noopener noreferrer"&gt;Amazon ECS Task Definition file&lt;/a&gt; to set parameter &lt;code&gt;readonlyRootFilesystem&lt;/code&gt;in section Storage and logging to &lt;code&gt;true&lt;/code&gt;, as the default value is &lt;code&gt;false&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Example &lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_task_definition" rel="noopener noreferrer"&gt;Terraform resource definition&lt;/a&gt;, see line 27:&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_ecs_task_definition" "hardened_task_definition" {
  family = "hardened-task-definition"
  network_mode = "awsvpc"
  requires_compatibilities = ["FARGATE"]
  cpu = 256
  memory = 512

  volume {
    name = "efs-volume"
    efs_volume_configuration {
      file_system_id = "fs-0123456789abcdef" # Replace with your EFS file system ID
      root_directory = "/tmp"
    }
  }

  container_definitions = jsonencode([
    {
      name = "hardened-container"
      image = "nginx:latest"
      essential = true
      portMappings = [
        {
          containerPort = 8080
          hostPort = 8080
        }
      ]
      readonlyRootFilesystem = true
      volumesFrom = [
        {
          sourceContainer = "efs-volume-container"
        }
      ]
    },
    {
      name = "efs-volume-container"
      image = "amazon/amazon-efs-utils:latest"
      essential = true
      volumeMounts = [
        {
          name = "efs-volume"
          mountPath = "/tmp"
        }
      ]
    }
  ])
}

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

&lt;/div&gt;



&lt;p&gt;With this configuration, the /tmp directory inside the hardened-container will be mounted to the specified EFS file system, allowing temporary files to be stored on the persistent EFS file system instead of the read-only root filesystem.&lt;/p&gt;

&lt;h4&gt;
  
  
  Immutability for workloads provisioned with AWS Elastic Kubernetes Service
&lt;/h4&gt;

&lt;p&gt;This also applies to Kubernetes in general. In the manifest file, specify &lt;code&gt;securityContext&lt;/code&gt;: &lt;code&gt;readOnlyRootFilesystem: true&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Example Kubernetes manifest below which demonstrates read only configuration on lines 11-12:&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
  labels:
    run: nginx
  name: nginx
spec:
  containers:
    - image: nginx
      name: hardened-container
      securityContext:
        readOnlyRootFilesystem: true
      volumeMounts:
        - name: cache-volume
          mountPath: /var/cache/nginx
        - name: runtime-volume
          mountPath: /var/run
        - name: efs-volume
          mountPath: /tmp
  volumes:
    - name: cache-volume
      emptyDir: {}
    - name: runtime-volume
      emptyDir: {}
    - name: efs-volume
      nfs:
        server: efs-server.default.svc.cluster.local
        path: "/efs-share"

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

&lt;/div&gt;



&lt;p&gt;After applying this updated manifest, the Nginx container will have an Amazon EFS volume mounted at /tmp, allowing it to use the persistent storage provided by Amazon EFS for temporary files while still maintaining a read-only root filesystem.&lt;/p&gt;

&lt;p&gt;Leverage Kubernetes’ Deployment strategies for blue/green, &lt;a href="https://kubernetes.io/docs/concepts/workloads/management/#canary-deployments" rel="noopener noreferrer"&gt;canary&lt;/a&gt;etc. as best suits your use-case.&lt;/p&gt;

&lt;h4&gt;
  
  
  About Docker labels/image tags and deployment
&lt;/h4&gt;

&lt;p&gt;When new Docker images are produced and pushed to a container repository, a common approach is to add the label/tag “latest” to it (similar to HEAD in Git), and this is also referenced in CI/CD pipelines. However, with this approach it is challenging to answer exactly which Docker image was in production at any given time, since the reference to the actual image is lost.&lt;/p&gt;

&lt;p&gt;To follow through on immutability it is recommended to set a unique tag on every new image. You can reference the actual image tag checksum or some other application specific tag like hello-world-v1.2.3 for &lt;a href="https://www.cncf.io/blog/2021/09/28/gitops-101-whats-it-all-about/" rel="noopener noreferrer"&gt;GitOps&lt;/a&gt; and CI/CD deployment. If you also add a tag with the Git commit ID, debugging becomes much easier.&lt;/p&gt;

&lt;p&gt;It also becomes more transparent when dealing with rollbacks when you know hello-world-v1.2.3 was live in production, hello-world-v1.2.4 failed the canary health checks, the automatic procedure rolled back to hello-world-v1.2.3, and you can quickly look up the git commit of hello-world-v1.2.4 for the change.&lt;/p&gt;

&lt;p&gt;In Amazon Elastic Container Registry you can &lt;a href="https://docs.aws.amazon.com/AmazonECR/latest/userguide/image-tag-mutability.html" rel="noopener noreferrer"&gt;prevent image tags from being overwritten&lt;/a&gt; by enabling the property Tag immutability.&lt;/p&gt;

&lt;h3&gt;
  
  
  Immutability for serverless workloads provisioned with AWS Lambda
&lt;/h3&gt;

&lt;p&gt;AWS Lambda is immutable by design. Lambda creates a new version of your function each time that you publish the function. It’s code, runtime, architecture, memory, layers, and most other configuration settings will remain unchanged.&lt;/p&gt;

&lt;p&gt;Versions can be used to control function deployment. You can publish a new version of a function for beta testing, or canary testing for a small amount of users instead of deploying the change to all production users at once by using a specific version reference (&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/configuration-versions.html#versioning-versions-using" rel="noopener noreferrer"&gt;Qualified ARN&lt;/a&gt; instead of $LATEST), in AWS API Gateway or other services that are routing traffic to your Lambda functions.&lt;/p&gt;

&lt;p&gt;In the example below we define three &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/configuration-aliases.html" rel="noopener noreferrer"&gt;aliases&lt;/a&gt;: staging, canary-prod and prod, which refers to relevant Lambda function versions. A &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/configuring-alias-routing.html" rel="noopener noreferrer"&gt;Lambda routing configuration&lt;/a&gt; is defined which directs 90% of the traffic to alias &lt;code&gt;prod&lt;/code&gt; (v41), and 10% to alias &lt;code&gt;canary-prod&lt;/code&gt; (v42).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Procedure&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Test version 42 in staging.&lt;/li&gt;
&lt;li&gt;Roll out version 42 to 10% of all production users.&lt;/li&gt;
&lt;li&gt;CloudWatch Metrics for Lambda functions are observed and grouped by a combination of alias and executed version.&lt;/li&gt;
&lt;li&gt;If application monitoring and health checks are sustained over a course of eg. 30 minutes with no increases in error rates, update the alias for prod to increase function_version from 41 to 42 and reset routing_config. If not, roll back.&lt;/li&gt;
&lt;li&gt;If health checks are still OK, end. If not, roll back.&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%2F8u9m0ebcm0ipb4ylgmn0.jpg" 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%2F8u9m0ebcm0ipb4ylgmn0.jpg" width="768" height="1300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Terraform example of AWS Lambda aliases and routing configuration for canary deployment:&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_lambda_alias" "staging" {
    name = "staging"
    function_name = "arn:aws:lambda:aws-region:123456789012:function:helloworld"
    function_version = "42"
    description = "Canary alias for production environment"
}

resource "aws_lambda_alias" "canary_prod" {
    name = "canary-prod"
    function_name = "arn:aws:lambda:aws-region:123456789012:function:helloworld"
    function_version = "42"
    description = "Canary alias for production environment"
  }

resource "aws_lambda_alias" "prod" {
  name = "prod"
  function_name = "arn:aws:lambda:aws-region:123456789012:function:helloworld"
  function_version = "41"
  description = "Alias for main production audience"
}

resource "aws_lambda_update_alias" "prod_routing_with_canary" {
  name = aws_lambda_alias.prod.name
  function_name = aws_lambda_alias.prod.function_name
  function_version = aws_lambda_alias.prod.function_version

  routing_config {
    additional_version_weights = {
      aws_lambda_alias.canary_prod.function_version = 0.1
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Data handling in the immutable infrastructure model
&lt;/h4&gt;

&lt;p&gt;As the compute resources themselves can come and go, data that needs to be persisted, including session state, has to be moved off of the compute tier. Depending on the type of data there are different AWS services at your disposal:&lt;/p&gt;

&lt;p&gt;| &lt;strong&gt;Data type&lt;/strong&gt; | &lt;strong&gt;Recommended AWS service&lt;/strong&gt; (as starting point) |&lt;br&gt;
| Application session data/state | &lt;a href="https://aws.amazon.com/elasticache/" rel="noopener noreferrer"&gt;Amazon ElastiCache Redis/Memcached&lt;/a&gt; |&lt;br&gt;
| Application cache and temporary data | &lt;a href="https://aws.amazon.com/efs/" rel="noopener noreferrer"&gt;Amazon Elastic File System (EFS) [“NFS”]&lt;/a&gt; |&lt;br&gt;
| Relational data | &lt;a href="https://aws.amazon.com/rds/aurora/" rel="noopener noreferrer"&gt;Amazon Aurora&lt;/a&gt; etc. |&lt;br&gt;
| Non-relational data | &lt;a href="https://aws.amazon.com/dynamodb/" rel="noopener noreferrer"&gt;Amazon DynamoDB&lt;/a&gt; etc. |&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;By adhering to the principle of never changing a running system, a higher degree of predictability and reliability can be achieved.&lt;/p&gt;

&lt;p&gt;Many view rollbacks as a pain. Most people avoid spending time on rollback and disaster recovery testing. “We don’t do rollbacks, we prefer to try to fix the problem and roll forward”. This is a warning signal of manual procedures and lacking automation.&lt;/p&gt;

&lt;p&gt;With the immutable infrastructure approach rollbacks becomes a natural part of change management. Database changes can be managed with the &lt;a href="https://www.prisma.io/dataguide/types/relational/expand-and-contract-pattern" rel="noopener noreferrer"&gt;Expand-Contract pattern&lt;/a&gt;. Any failing automated checks with increased error rates should automatically roll back to the previously known working version, also supported by the database schema. By adopting canary releases and/or blue-green deployments rollbacks can be performed fast and with reduced end user impact.&lt;/p&gt;

&lt;p&gt;A bonus with the Immutable Infrastructure approach is that rollbacks will become trivial. By releasing small changes frequently a failed new release shouldn’t be a big issue. Instead of being up at night or feeling the pressure of having to fight fires to resolve issues which are impacting end users, teams can investigate in peace and quiet, produce a new artifact version with the bug-fix and ship it quickly through fully automated and quality assured CI/CD pipelines, to master the art of &lt;a href="https://continuousdelivery.com/principles/" rel="noopener noreferrer"&gt;Continuous Delivery&lt;/a&gt; and realize business value &lt;em&gt;faster&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://repost.aws/knowledge-center/ec2-instance-crowdstrike-agent" rel="noopener noreferrer"&gt;https://repost.aws/knowledge-center/ec2-instance-crowdstrike-agent&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href="https://martinfowler.com/bliki/ImmutableServer.html" rel="noopener noreferrer"&gt;https://martinfowler.com/bliki/ImmutableServer.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/wellarchitected/latest/framework/rel_tracking_change_management_immutable_infrastructure.html" rel="noopener noreferrer"&gt;AWS Well-Architected Framework REL08-BP04 Deploy using immutable infrastructure&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/wellarchitected/latest/framework/rel_tracking_change_management_automated_changemgmt.html" rel="noopener noreferrer"&gt;AWS Well-Architected Framework REL08-BP05 Deploy changes with automation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/wellarchitected/latest/operational-excellence-pillar/ops_mit_deploy_risks_auto_testing_and_rollback.html" rel="noopener noreferrer"&gt;AWS Well-Architected Framework OPS06-BP04 Automate testing and rollback&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/wellarchitected/latest/operational-excellence-pillar/ops_dev_integ_auto_integ_deploy.html" rel="noopener noreferrer"&gt;AWS Well-Architected Framework OPS05-BP09 Make frequent, small, reversible changes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://12factor.net/processes%20" rel="noopener noreferrer"&gt;https://12factor.net/processes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://netflixtechblog.com/ami-creation-with-aminator-98d627ca37b0%20" rel="noopener noreferrer"&gt;https://netflixtechblog.com/ami-creation-with-aminator-98d627ca37b0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/imagebuilder/latest/userguide/what-is-image-builder.html" rel="noopener noreferrer"&gt;AWS EC2 Image Builder User Guide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The post &lt;a href="https://hedrange.com/2024/08/09/move-fast-and-avoid-surprises-increase-system-reliability-with-immutable-infrastructure/" rel="noopener noreferrer"&gt;Increase system reliability with Immutable Infrastructure – Move fast and avoid surprises&lt;/a&gt; first appeared on &lt;a href="https://hedrange.com" rel="noopener noreferrer"&gt;Håkon Eriksen Drange&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>immutability</category>
      <category>wellarchitected</category>
      <category>bastion</category>
    </item>
    <item>
      <title>Bye bye Bastion!</title>
      <dc:creator>Håkon Eriksen Drange</dc:creator>
      <pubDate>Wed, 03 Jul 2024 17:27:38 +0000</pubDate>
      <link>https://dev.to/haakoned/bye-bye-bastion-3dj2</link>
      <guid>https://dev.to/haakoned/bye-bye-bastion-3dj2</guid>
      <description>&lt;h5&gt;
  
  
  &lt;strong&gt;Table of contents&lt;/strong&gt;
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;
Exploring alternative workflows

&lt;ul&gt;
&lt;li&gt;Deploy sample infrastructure&lt;/li&gt;
&lt;li&gt;Well-Architected Virtual Private Cloud (VPC)&lt;/li&gt;
&lt;li&gt;RDS Aurora MySQL Multi-AZ cluster&lt;/li&gt;
&lt;li&gt;EC2 Amazon Linux instance and security group&lt;/li&gt;
&lt;li&gt;AWS Cloud9 SSM Managed Instance&lt;/li&gt;
&lt;li&gt;Alternative 1: AWS Systems Manager – Session Manager&lt;/li&gt;
&lt;li&gt;Prerequisites&lt;/li&gt;
&lt;li&gt;Connecting to an EC2 instance in a private subnet from the AWS Console&lt;/li&gt;
&lt;li&gt;Connecting to an EC2 instance in a private subnet from your local workstation with the AWS CLI and AWS IAM Identity Center&lt;/li&gt;
&lt;li&gt;Connecting to an RDS cluster in a private database subnet with local port forwarding&lt;/li&gt;
&lt;li&gt;Alternative 2: AWS CloudShell VPC Environment&lt;/li&gt;
&lt;li&gt;Alternative 3: AWS Cloud9 IDE&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Conclusion and feature comparison&lt;/li&gt;

&lt;li&gt;References&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://aws.amazon.com/solutions/implementations/linux-bastion/" rel="noopener noreferrer"&gt;Bastion Host&lt;/a&gt;, or Jump Host concept, has historically been a traditional pattern for providing system administrators with external access to internal compute resources on distinct networks. An actor connects to a dedicated host in a DMZ by most commonly Secure Shell (SSH) or Remote Desktop Protocol (RDP), and from there gain access to compute resources on internal networks to perform system maintenance, apply patches, check content in a database , update schemas etc.&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%2Fuctxfx4vy6h1mip2iolb.jpg" 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%2Fuctxfx4vy6h1mip2iolb.jpg" width="797" height="219"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These Bastion Hosts must behighly secured to withstand attacks. Measures include hardened operating systems and server configurations, removing non-required services and libraries, firewalling and audit logging, but the reality is that many instances do not meet the recommended standards, either by lack of knowledge or configuration mistakes. One of the most common attack vectors is simply not locking down the firewall rules/security group rules to only permit access on relevant ports from trusted IP ranges, leaving SSH/RDP open to anyone (0.0.0.0/0) instead of your corporate gateway or VPN.&lt;/p&gt;

&lt;p&gt;As mentioned in my post &lt;a href="https://hedrange.com/2024/05/23/protect-your-webapps-from-malicious-traffic-with-aws-web-application-firewall/" rel="noopener noreferrer"&gt;Protect your webapps from malicious traffic with AWS Web Application Firewall&lt;/a&gt;, _ &lt;strong&gt;7 percent of EC2 instances&lt;/strong&gt; ,  &lt;strong&gt;3 percent of Azure VMs&lt;/strong&gt; , and  &lt;strong&gt;13 percent of Google Cloud VMs&lt;/strong&gt;  are publicly exposed to the internet. Among instances that are publicly exposed, HTTP and HTTPS are the most commonly exposed ports, and are not considered risky in general. After these, SSH and RDP remote access protocols are common._&lt;/p&gt;

&lt;p&gt;On July 2nd 2024 the critical vulnerability &lt;a href="https://www.qualys.com/regresshion-cve-2024-6387/" rel="noopener noreferrer"&gt;CVE-2024-6387, labeled regreSSHion&lt;/a&gt;, was announced, where an unauthenticated remote code execution in OpenSSH’s server (sshd) could grant full root access. With a vulnerability score of 9.8/10 this is one of the most serious bugs in OpenSSH in years.&lt;/p&gt;

&lt;p&gt;The first thing to do is to stop exposing SSH/RDP, and then find alternative, more modern workflows for accessing cloud resources. Even if access is whitelisted from trusted IP address ranges, it’s a bad practice in 2024 to have direct access into production environments.&lt;/p&gt;

&lt;p&gt;As stated in the AWS Well-Architected Framework – Security Pillar:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Use automation to perform deployment, configuration, maintenance, and investigative tasks wherever possible. Consider manual access to compute resources in cases of emergency procedures or in safe (sandbox) environments, when automation is not available.&lt;/em&gt;  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Common anti-patterns&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Interactive access to Amazon EC2 instances with protocols such as SSH or RDP.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Maintaining individual user logins such as &lt;code&gt;/etc/passwd&lt;/code&gt; or Windows local users.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Sharing a password or private key to access an instance among multiple users.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Manually installing software and creating or updating configuration files.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Manually updating or patching software.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Logging into an instance to troubleshoot problems.&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Removing the use of Secure Shell (SSH) and Remote Desktop Protocol (RDP) for interactive access reduces the scope of access to your compute resources. This takes away a common path for unauthorized actions.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;cite&gt;&lt;a href="https://docs.aws.amazon.com/wellarchitected/latest/framework/sec_protect_compute_reduce_manual_management.html" title="" rel="noopener noreferrer"&gt;SEC06-BP03 Reduce manual management and interactive access&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For reference, the &lt;a href="https://docs.aws.amazon.com/securityhub/latest/userguide/cis-aws-foundations-benchmark.html" rel="noopener noreferrer"&gt;CIS AWS Foundations Benchmark&lt;/a&gt; has multiple controls when it comes to detecting public exposure of SSH/RDP. &lt;a href="https://aws.amazon.com/security-hub/" rel="noopener noreferrer"&gt;AWS Security Hub&lt;/a&gt; &lt;a href="https://aws.amazon.com/config/" rel="noopener noreferrer"&gt;AWS Config&lt;/a&gt; and &lt;a href="https://aws.amazon.com/premiumsupport/technology/trusted-advisor/" rel="noopener noreferrer"&gt;AWS Trusted Advisor&lt;/a&gt; can help you here.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/securityhub/latest/userguide/ec2-controls.html#ec2-53" rel="noopener noreferrer"&gt;[EC2.53]&lt;/a&gt; EC2 security groups should not allow ingress from 0.0.0.0/0 to remote server administration ports

&lt;ul&gt;
&lt;li&gt;This control checks whether an Amazon EC2 security group allows ingress from 0.0.0.0/0 to remote server administration ports (ports 22 and 3389). The control fails if the security group allows ingress from 0.0.0.0/0 to port 22 or 3389.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://docs.aws.amazon.com/securityhub/latest/userguide/ec2-controls.html#ec2-13" rel="noopener noreferrer"&gt;[EC2.13]&lt;/a&gt; Security groups should not allow ingress from 0.0.0.0/0 or ::/0 to port 22

&lt;ul&gt;
&lt;li&gt;This control checks whether an Amazon EC2 security group allows ingress from 0.0.0.0/0 or ::/0 to port 22. The control fails if the security group allows ingress from 0.0.0.0/0 or ::/0 to port 22.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://docs.aws.amazon.com/securityhub/latest/userguide/ec2-controls.html#ec2-21" rel="noopener noreferrer"&gt;[EC2.21]&lt;/a&gt; Network ACLs should not allow ingress from 0.0.0.0/0 to port 22 or port 3389

&lt;ul&gt;
&lt;li&gt;This control checks whether a network access control list (network ACL) allows unrestricted access to the default TCP ports for SSH/RDP ingress traffic. The control fails if the network ACL inbound entry allows a source CIDR block of ‘0.0.0.0/0’ or ‘::/0’ for TCP ports 22 or 3389.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Exploring alternative workflows
&lt;/h2&gt;

&lt;p&gt;It’s encouraged to pivot from a classical systems administration approach and substitute interactive access with &lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/what-is-systems-manager.html" rel="noopener noreferrer"&gt;AWS Systems Manager&lt;/a&gt; capabilities.&lt;/p&gt;

&lt;p&gt;Look into how you can automate runbooks and trigger maintenance tasks with &lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-automation.html" rel="noopener noreferrer"&gt;AWS Systems Manager Automation documents&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Don’t perform changes in live systems, deploy EC2 compute resources using the &lt;a href="https://docs.aws.amazon.com/wellarchitected/latest/framework/rel_tracking_change_management_immutable_infrastructure.html" rel="noopener noreferrer"&gt;immutable infrastructure pattern&lt;/a&gt;, as for container based workloads. Or, even better, containerize to AWS ECS Fargate/EKS.&lt;/p&gt;

&lt;p&gt;If your use-case dictates interactive access; disable security group ingress rules for port 22/tcp (SSH) or port 3389/tcp (RDP) and leverage AWS SSM – &lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager.html" rel="noopener noreferrer"&gt;Session Manager&lt;/a&gt; agent based access to EC2. You can also configure activity logging to Amazon CloudWatch Logs for a full audit trail. We will explore this workflow in the coming chapters.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploy sample infrastructure
&lt;/h3&gt;

&lt;p&gt;For testing the described alternatives I have developed a sample Terraform module which deploys the following resources.&lt;/p&gt;

&lt;h5&gt;
  
  
  Well-Architected Virtual Private Cloud (VPC)
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;Public subnets&lt;/li&gt;
&lt;li&gt;Private subnets with VPC endpoints&lt;/li&gt;
&lt;li&gt;Database subnets&lt;/li&gt;
&lt;li&gt;NAT Gateways&lt;/li&gt;
&lt;li&gt;VPC endpoints&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;locals {
  azs = slice(data.aws_availability_zones.available.names, 0, 2)
  aws_account_id = data.aws_caller_identity.current.account_id
}

module "vpc" {
  source = "git::https://github.com/terraform-aws-modules/terraform-aws-vpc.git?ref=25322b6b6be69db6cca7f167d7b0e5327156a595"

  name = var.name_prefix
  cidr = var.vpc_cidr
  azs = local.azs
  private_subnets = [for k, v in local.azs : cidrsubnet(var.vpc_cidr, 8, k)]
  public_subnets = [for k, v in local.azs : cidrsubnet(var.vpc_cidr, 8, k + 4)]
  database_subnets = [for k, v in local.azs : cidrsubnet(var.vpc_cidr, 8, k + 8)]

  create_database_subnet_group = true
  create_database_subnet_route_table = true
  create_database_internet_gateway_route = false

  manage_default_network_acl = true
  manage_default_route_table = true
  manage_default_security_group = true

  enable_dns_hostnames = true
  enable_dns_support = true
  enable_nat_gateway = true
  single_nat_gateway = false
  one_nat_gateway_per_az = true

  enable_flow_log = true
  create_flow_log_cloudwatch_log_group = true
  create_flow_log_cloudwatch_iam_role = true
  flow_log_max_aggregation_interval = 60

  vpc_tags = {
    Name = var.name_prefix
  }
}

module "vpc_endpoints" {
  source = "git::https://github.com/terraform-aws-modules/terraform-aws-vpc.git//modules/vpc-endpoints?ref=4a2809c673afa13097af98c2e3c553da8db766a9"

  vpc_id = module.vpc.vpc_id

  create_security_group = true
  security_group_name_prefix = "${var.name_prefix}-vpc-endpoints-"
  security_group_description = "VPC endpoint security group"
  security_group_rules = {
    ingress_https = {
      description = "HTTPS from VPC"
      cidr_blocks = [module.vpc.vpc_cidr_block]
    }
  }

  endpoints = {
    dynamodb = {
      service = "dynamodb"
      service_type = "Gateway"
      route_table_ids = flatten([module.vpc.intra_route_table_ids, module.vpc.private_route_table_ids, module.vpc.public_route_table_ids])
      policy = data.aws_iam_policy_document.dynamodb_endpoint_policy.json
      tags = { Name = "dynamodb-vpc-endpoint" }
    },
    ecs = {
      service = "ecs"
      private_dns_enabled = true
      subnet_ids = module.vpc.private_subnets
    },
    ecs_telemetry = {
      create = false
      service = "ecs-telemetry"
      private_dns_enabled = true
      subnet_ids = module.vpc.private_subnets
    },
    ecr_api = {
      service = "ecr.api"
      private_dns_enabled = true
      subnet_ids = module.vpc.private_subnets
      policy = data.aws_iam_policy_document.generic_endpoint_policy.json
    },
    ecr_dkr = {
      service = "ecr.dkr"
      private_dns_enabled = true
      subnet_ids = module.vpc.private_subnets
      policy = data.aws_iam_policy_document.generic_endpoint_policy.json
    },
    rds = {
      service = "rds"
      private_dns_enabled = true
      subnet_ids = module.vpc.private_subnets
      security_group_ids = [module.db.security_group_id]
    },
    kms = {
      service = "kms"
      private_dns_enabled = true
      subnet_ids = module.vpc.database_subnets
    },
    ssm = {
      service = "ssm"
      private_dns_enabled = true
      subnet_ids = module.vpc.private_subnets
    },
    ssmmessages = {
      service = "ssmmessages"
      private_dns_enabled = true
      subnet_ids = module.vpc.private_subnets
    },
    ec2messages = {
      service = "ec2messages"
      private_dns_enabled = true
      subnet_ids = module.vpc.private_subnets
    }
  }

  tags = {
    Name = var.name_prefix
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  RDS Aurora MySQL Multi-AZ cluster
&lt;/h5&gt;

&lt;p&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module "db" {
  source = "git::https://github.com/terraform-aws-modules/terraform-aws-rds-aurora.git?ref=7d46e900b31322fd7a0ab0d7f67006ba4836c995"

  name = "${var.name_prefix}-rds"
  engine = "aurora-mysql"
  engine_version = "8.0"
  master_username = "root"
  instances = {
    1 = {
      instance_class = "db.t3.medium"
    }
    2 = {
      instance_class = "db.t3.medium"
    }
  }
  vpc_id = module.vpc.vpc_id
  db_subnet_group_name = module.vpc.database_subnet_group_name
  security_group_rules = {
    ingress = {
      source_security_group_id = aws_security_group.private_access.id
    }
    ingress = {
      source_security_group_id = data.aws_security_group.cloud9_security_group.id
    }
    kms_vpc_endpoint = {
      type = "egress"
      from_port = 443
      to_port = 443
      source_security_group_id = module.vpc_endpoints.security_group_id
    }
  }

  tags = {
    Name = var.name_prefix
    Environment = "dev"
    Classification = "internal"
  }

  manage_master_user_password_rotation = true
  master_user_password_rotation_schedule_expression = "rate(7 days)"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  EC2 Amazon Linux instance and security group
&lt;/h5&gt;

&lt;p&gt;The Security Group for RDS is provisioned within the module. One placeholder security group labeled “private_access” is defined for EC2 and CloudShell purposes which only permits egress traffic. It is referenced in the RDS cluster security group to permit incoming connections on port 3306 for MySQL. This is called &lt;a href="https://docs.aws.amazon.com/vpc/latest/userguide/security-group-rules.html#security-group-referencing" rel="noopener noreferrer"&gt;security group referencing&lt;/a&gt; and allows for dynamic configurations, instead of specifying static CIDR ranges, which often are too permissive.&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%2Fky7530bxir8v5a2f56y8.jpg" 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%2Fky7530bxir8v5a2f56y8.jpg" width="800" height="676"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data "aws_ami" "amazon_linux_23" {
  most_recent = true
  owners = ["amazon"]

  filter {
    name = "name"
    values = ["al2023-ami-2023*-x86_64"]
  }
}

module "ec2_instance" {
  source = "git::https://github.com/terraform-aws-modules/terraform-aws-ec2-instance.git?ref=4f8387d0925510a83ee3cb88c541beb77ce4bad6"

  name = "${var.name_prefix}-ec2"
  ami = data.aws_ami.amazon_linux_23.id
  create_iam_instance_profile = true
  iam_role_description = "IAM role for EC2 instance and SSM access"
  iam_role_policies = {
    AmazonSSMManagedInstanceCore = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
  }
  instance_type = "t2.micro"
  vpc_security_group_ids = [aws_security_group.private_access.id]
  subnet_id = element(module.vpc.private_subnets, 0)

  # Enforces IMDSv2
  metadata_options = {
    "http_endpoint" : "enabled",
    "http_put_response_hop_limit" : 1,
    "http_tokens" : "required"
  }

  tags = {
    Name = "${var.name_prefix}-ec2"
    Environment = "dev"
  }
}

resource "aws_security_group" "private_access" {
  #checkov:skip=CKV2_AWS_5: Placeholder security group, to be assigned to applicable resources, but beyond scope of this module.
  name_prefix = "${var.name_prefix}-private-access"
  description = "Security group for private access from local resources. Permits egress traffic."
  vpc_id = module.vpc.vpc_id

  egress {
    description = "Permit egress TCP"
    from_port = 0
    to_port = 65535
    protocol = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  egress {
    description = "Permit egress UDP"
    from_port = 0
    to_port = 65535
    protocol = "udp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  egress {
    description = "Permit egress ICMP"
    from_port = -1
    to_port = -1
    protocol = "icmp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "${var.name_prefix}-sg-private-access"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  AWS Cloud9 SSM Managed Instance
&lt;/h5&gt;

&lt;p&gt;The data property for the security group makes it possible to identity the security group provisioned by AWS Cloud9. This is added in an ingress rule for the RDS cluster, as previously defined.&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_cloud9_environment_ec2" "cloud9_ssm_instance" {
  name = "${var.name_prefix}-cloud9"
  instance_type = "t2.micro"
  automatic_stop_time_minutes = 30
  image_id = "amazonlinux-2023-x86_64"
  connection_type = "CONNECT_SSM"
  subnet_id = element(module.vpc.private_subnets, 0)
  owner_arn = length(var.cloud9_instance_owner_arn) &amp;gt; 0 ? var.cloud9_instance_owner_arn : null
}

data "aws_security_group" "cloud9_security_group" {
  filter {
    name = "tag:aws:cloud9:environment"
    values = [
      aws_cloud9_environment_ec2.cloud9_ssm_instance.id
    ]
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a fully working code repository along with setup instructions, see &lt;a href="https://github.com/haakond/terraform-aws-bastion-host-alternatives/blob/main/examples/main.tf" rel="noopener noreferrer"&gt;https://github.com/haakond/terraform-aws-bastion-host-alternatives/blob/main/examples/main.tf&lt;/a&gt; and &lt;a href="https://github.com/haakond/terraform-aws-bastion-host-alternatives/blob/main/README.md" rel="noopener noreferrer"&gt;https://github.com/haakond/terraform-aws-bastion-host-alternatives/blob/main/README.md&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Alternative 1: AWS Systems Manager – Session Manager
&lt;/h3&gt;

&lt;p&gt;With &lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager.html" rel="noopener noreferrer"&gt;AWS Systems Manager – Session Manager&lt;/a&gt;, you can manage your Amazon Elastic Compute Cloud (Amazon EC2) instances, edge devices, on-premises servers, and virtual machines (VMs). Port forwarding is also supported to connect to remote hosts in private subnets.&lt;/p&gt;

&lt;p&gt;You can use either an interactive one-click browser-based shell or the AWS Command Line Interface (AWS CLI). Session Manager provides secure and auditable node management without the need to open inbound ports, maintain bastion hosts, or manage SSH keys. Session Manager supports Linux, Windows and macOS and session activity can be logged with AWS CloudTrail and Amazon CloudWatch Logs.&lt;/p&gt;

&lt;p&gt;Session Manager can be configured to &lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-logging.html" rel="noopener noreferrer"&gt;log entered commands and their output during a session&lt;/a&gt;, which can be used for generating reports or audit situations.&lt;/p&gt;

&lt;p&gt;Note: AWS Systems Manager also provides an option with &lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/connect-with-ec2-instance-connect-endpoint.html" rel="noopener noreferrer"&gt;EC2 Instance Connect Endpoints&lt;/a&gt;, but this is based on SSH.&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%2F22dlc3hpqa661hscdoau.jpg" 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%2F22dlc3hpqa661hscdoau.jpg" width="800" height="515"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The coming two examples are based on this access pattern.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/operating-systems-and-machine-types.html" rel="noopener noreferrer"&gt;Supported operating system&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/ssm-agent.html" rel="noopener noreferrer"&gt;AWS Systems Manager SSM agent installed&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/ami-preinstalled-agent.html" rel="noopener noreferrer"&gt;List of AMIs with the SSM Agent preinstalled&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-getting-started-privatelink.html" rel="noopener noreferrer"&gt;Connectivity to endpoints&lt;/a&gt; ec2messages, ssm and ssmmessages in the current region&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-getting-started-instance-profile.html" rel="noopener noreferrer"&gt;IAM service role permissions&lt;/a&gt; AmazonSSMManagedInstanceCore or equivalent&lt;/li&gt;

&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html" rel="noopener noreferrer"&gt;Optional: Install the Session Manager plugin for the AWS CLI&lt;/a&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;h4&gt;
  
  
  Connecting to an EC2 instance in a private subnet from the AWS Console
&lt;/h4&gt;

&lt;p&gt;There are two optional starting points in the AWS Console, either from the AWS Systems Manager – Session Manager or directly from the EC2 instances list. The EC2 approach is usually the fastest and most convenient. With the prerequisites in order, find the instance you want to connect to, hit Connect.&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%2Fyyu2o8q22e64l0azsg2m.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%2Fyyu2o8q22e64l0azsg2m.png" width="797" height="123"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ensure the tab with the option Session Manager is chosen and click Connect again.&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%2F8ivnp0tdtijw6f90jrwn.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%2F8ivnp0tdtijw6f90jrwn.png" width="800" height="386"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I am now logged in and authenticated with my Federated AWS IAM Identity Center method and we have full traceability.&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%2Fu7ly1rymvoarl4pulr31.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%2Fu7ly1rymvoarl4pulr31.png" width="800" height="307"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Connecting to an EC2 instance in a private subnet from your local workstation with the AWS CLI and AWS IAM Identity Center
&lt;/h4&gt;

&lt;p&gt;In this example Microsoft Entra ID is the identity provider and &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers.html" rel="noopener noreferrer"&gt;federation&lt;/a&gt;is configured with AWS IAM Identity Center for modern user management. Do yourself a favor and get rid of those IAM users with static access keys.&lt;/p&gt;

&lt;p&gt;To authorize your workstation based on Linux, macOS or Windows Subsystem for Linux, ensure you have the &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html" rel="noopener noreferrer"&gt;latest version of the AWS CLI installed&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Run &lt;code&gt;aws configure sso&lt;/code&gt; and follow the instructions to obtain a valid session based on a browser where you’re currently logged in. For a full step by step guide see &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/sso-configure-profile-token.html#sso-configure-profile-token-auto-sso" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/cli/latest/userguide/sso-configure-profile-token.html#sso-configure-profile-token-auto-sso&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You should now be logged in and have chosen the relevant AWS account and role to assume.&lt;/p&gt;

&lt;p&gt;To confirm I run the following AWS cli command to get a list of EC2 instances including the Name tag value. One instance is returned.&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aws ec2 describe-instances --query 'Reservations[].Instances[].[InstanceId, Tags[?Key==Name].Value[]]' --output=json

[
    [
        [
            "i-0b448a908727eec7d",
            [
                "bastion-alternative-demo-ec2"
            ]
        ]
    ]
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Connecting to the instance is as simple as:&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aws ssm start-session --target i-0b448a908727eec7d
&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%2F3qh9tjngmspttmb5j3hb.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%2F3qh9tjngmspttmb5j3hb.png" width="800" height="324"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Type &lt;code&gt;exit&lt;/code&gt;to terminate the session. There you go, CLI or Console based access with a security group with no inbound rules.&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%2F1jjvuxw1whuw6oe9ontc.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%2F1jjvuxw1whuw6oe9ontc.png" width="800" height="275"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Connecting to an RDS cluster in a private database subnet with local port forwarding
&lt;/h4&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%2F7cfrgbapmt7vu9631alw.jpg" 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%2F7cfrgbapmt7vu9631alw.jpg" width="800" height="337"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As with the previous example, establish a session with &lt;code&gt;aws configure sso&lt;/code&gt; and identify the EC2 instance you would like to use as proxy. In this example we use the same one. To find the RDS cluster writer endpoint name you can issue the following command:&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aws rds describe-db-clusters \
  --query 'DBClusters[?starts_with(DBClusterIdentifier, `bastion-alternative-demo`)].DBClusterIdentifier' \
  --output text | xargs -I {} aws rds describe-db-cluster-endpoints \
  --db-cluster-identifier {} \
  --query 'DBClusterEndpoints[?EndpointType==`WRITER`].Endpoint' \
  --output text
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alternatively, in the AWS Console navigate to RDS and copy the Writer Endpoint name, FQDN.&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%2Fgn9bpqxlqhju2214l2n9.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%2Fgn9bpqxlqhju2214l2n9.png" width="800" height="464"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Execute the following command to start a port forwarding session which should provide MySQL connectivity to port 3306 on your local machine:&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aws ssm start-session --target i-0b448a908727eec7d --document-name AWS-StartPortForwardingSessionToRemoteHost --parameters '{"portNumber":["3306"],"localPortNumber":["3306"],"host":["bastion-alternative-demo-rds.cluster-cmikipz5ncly.eu-west-1.rds.amazonaws.com"]}'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The main difference is that this time the &lt;code&gt;aws ssm start-session&lt;/code&gt; command will trigger an AWS Systems Managed Document called “AWS-StartPortForwardingSessionToRemoteHost” and we supply desired parameters. For demonstration the Terraform module has provisioned an RDS Aurora MySQL cluster in private database subnets.&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%2Ffvxvajxr31iqm7hf2x7s.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%2Ffvxvajxr31iqm7hf2x7s.png" width="800" height="118"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The command outputs “Starting session with SessionId (..) Port 3306 opened, Waiting for connections.&lt;/p&gt;

&lt;p&gt;In another terminal I run &lt;code&gt;mysql -y root -p -h 127.0.0.1 --port 3306&lt;/code&gt; and take the opportunity to create a new privileged mysql user called chuck_norris.&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%2F1yl5a58o4b8t66er8jen.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%2F1yl5a58o4b8t66er8jen.png" width="800" height="487"&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%2F5q06dtvdnp8bd7htvio5.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%2F5q06dtvdnp8bd7htvio5.png" width="558" height="461"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Verified working as expected.&lt;/p&gt;

&lt;p&gt;If you prefer to use a GUI client like &lt;a href="https://www.mysql.com/products/workbench/" rel="noopener noreferrer"&gt;MySQL Workbench&lt;/a&gt; or &lt;a href="https://www.heidisql.com/#google_vignette" rel="noopener noreferrer"&gt;HeidiSQL&lt;/a&gt;configure it to connect on localhost port 3306. If you run a development database server locally you probably would like to configure port forwarding to a different port.&lt;/p&gt;

&lt;h3&gt;
  
  
  Alternative 2: AWS CloudShell VPC Environment
&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%2Fymj9ncitz3k8ebdarlij.jpg" 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%2Fymj9ncitz3k8ebdarlij.jpg" width="800" height="515"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AWS CloudShell is a browser-based shell that is pre-authenticated with your console credentials which makes it easy to securely manage, explore and interact with AWS resources. Common development tools related to AWS are also pre-installed.&lt;/p&gt;

&lt;p&gt;AWS &lt;a href="https://aws.amazon.com/about-aws/whats-new/2024/06/aws-cloudshell-amazon-virtual-private-cloud/" rel="noopener noreferrer"&gt;announced&lt;/a&gt;VPC Environment support for AWS CloudShell on June 26th 2024. This enables the possibility to use CloudShell securely within the same subnet as other resources in your VPCs without the need for additional network configuration. Before this there was no means to control the network flow.&lt;/p&gt;

&lt;p&gt;One caveat is that AWS CloudShell VPC environment does not support persistent storage, as the regular CloudShell feature has. Storage is ephemeral and data and home directories are deleted when an active environment ends, so you have to ensure data you care about is saved in Amazon S3 or another relevant persistent store. In my opinion, from a security perspective, auto-cleanup is a positive thing.&lt;/p&gt;

&lt;p&gt;Open Cloudshell from the main AWS Services search box or the logo icon:&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%2F0o5mpg2owuszxca8skfj.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%2F0o5mpg2owuszxca8skfj.png" width="800" height="241"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We choose the pre-provisioned VPC, subnet and security group:&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%2Fy65k278dik1rhwn4tevz.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%2Fy65k278dik1rhwn4tevz.png" width="522" height="793"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s verify outbound connectivity:&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%2F6t0uj27vkwpgmvgrj8w7.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%2F6t0uj27vkwpgmvgrj8w7.png" width="800" height="313"&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%2Fx17epgmy5j44lkgdcpio.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%2Fx17epgmy5j44lkgdcpio.png" width="460" height="127"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The /home partition has about 12GB free space. If you need more scratch space, look into mounting &lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AmazonEFS.html" rel="noopener noreferrer"&gt;Amazon Elastic File System&lt;/a&gt; or dump stuff on &lt;a href="https://aws.amazon.com/s3/" rel="noopener noreferrer"&gt;Amazon S3&lt;/a&gt;. Keep in mind that the CloudShell volume is ephemeral and data will be gone after your session ends.&lt;/p&gt;

&lt;p&gt;Let’s also check that Cloudshell’s Elastic Network Interface is in the expected private subnet. Thank you &lt;a href="https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/q-in-IDE-setup.html" rel="noopener noreferrer"&gt;Amazon Q&lt;/a&gt; for the creative query suggestion.&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aws ec2 describe-network-interfaces \
  --filters "Name=private-ip-address,Values=$(ip addr show ens6 | grep -oE '((1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])\.){3}(1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])' | head -n 1)" \
  --query 'NetworkInterfaces[*].{SubnetId:SubnetId}' \
  --output text | xargs -I {} aws ec2 describe-subnets --subnet-ids {} \
  --query 'Subnets[*].{SubnetId:SubnetId,Ipv4CidrBlock:CidrBlock,TagName:Tags[?Key==`Name`].Value|[0]}' --output table
&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%2Fjengc0gzcybhfhd2f89f.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%2Fjengc0gzcybhfhd2f89f.png" width="800" height="125"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cloudshell has many development tools pre-installed, including a mysql client. To verify that the endpoint hostname resolves to a private ip address in the private subnet, install dig from bind-utils:&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo yum install bind-utils -y
dig +short &amp;lt;hostname&amp;gt;
&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%2Ffehpblnv7dnsewtio4em.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%2Ffehpblnv7dnsewtio4em.png" width="792" height="41"&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%2Fheviw11gc6akc4pke5n5.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%2Fheviw11gc6akc4pke5n5.png" width="800" height="328"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We successfully managed to connect to the RDS Aurora MySQL cluster and created a new database.&lt;/p&gt;

&lt;h3&gt;
  
  
  Alternative 3: AWS Cloud9 IDE
&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%2F8pyhkb1cr5xz0jxyvhx6.jpg" 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%2F8pyhkb1cr5xz0jxyvhx6.jpg" width="800" height="515"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/cloud9/latest/user-guide/welcome.html" rel="noopener noreferrer"&gt;AWS Cloud9&lt;/a&gt; is a cloud based integrated development environment (IDE) that can be accessed through the AWS Console. It could be an alternative if you prefer a lightweight client/workstation environment, where your development environment will be the configured the same across client devices.&lt;/p&gt;

&lt;p&gt;Cloud9 is a fully managed service based on EC2 and EBS for persistent data storage. Instance hibernation takes place after a period if inactivity (configurable, from 30 minutes to x hours) to save costs when not in use. A Cloud9 &lt;a href="https://docs.aws.amazon.com/cloud9/latest/user-guide/environments.html" rel="noopener noreferrer"&gt;environment&lt;/a&gt;can be deployed into both public and private subnets, in modes &lt;a href="https://docs.aws.amazon.com/cloud9/latest/user-guide/create-environment-main.html" rel="noopener noreferrer"&gt;EC2&lt;/a&gt;(recommended) or &lt;a href="https://docs.aws.amazon.com/cloud9/latest/user-guide/create-environment-ssh.html" rel="noopener noreferrer"&gt;SSH&lt;/a&gt;(discouraged). The EC2 mode supports the “&lt;a href="https://docs.aws.amazon.com/cloud9/latest/user-guide/ec2-ssm.html" rel="noopener noreferrer"&gt;no-ingress instance&lt;/a&gt;” pattern, without the need to open any inbound ports, by leveraging AWS Systems Manager. This is the procedure we will explore further.&lt;/p&gt;

&lt;p&gt;A sample Cloud9 instance has been provisioned as part of the Terraform sample module. Navigate to AWS Cloud9 in the AWS Console, locate “bastion-alternative-demo-cloud9” and click Open.&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%2Ffy0ql6xp81darx1djjmd.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%2Ffy0ql6xp81darx1djjmd.png" width="799" height="181"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The EC2 environment type comes with &lt;a href="https://docs.aws.amazon.com/cloud9/latest/user-guide/security-iam.html#auth-and-access-control-temporary-managed-credentials" rel="noopener noreferrer"&gt;AWS managed temporary credentials&lt;/a&gt; activated by default, which manages the AWS access credentials on the users behalf, with certain restrictions. To ensure you get all privileges available to the IAM policies for your active role session, disable this in the AWS Cloud9 main window, Preferences, AWS Settings, Credentials.&lt;/p&gt;

&lt;p&gt;Open a Terminal tab and issue &lt;code&gt;aws configure sso&lt;/code&gt; as in the previous example, and set the SSO session name “default”.&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%2F9pwm2mj9xdhh9vwpdarr.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%2F9pwm2mj9xdhh9vwpdarr.png" width="799" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;aws s3 ls&lt;/code&gt; verifies the AWS session. Technically, this could have been executed from “anywhere”, but the main benefit are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Cloud9 environment maintains the configuration regardless of client device. &lt;/li&gt;
&lt;li&gt;Other resources on private subnets may be configured to be available.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Verify database connectivity:&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%2Fo0ty73b2hbhf2eaxmqgm.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%2Fo0ty73b2hbhf2eaxmqgm.png" width="800" height="282"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion and feature comparison
&lt;/h2&gt;

&lt;p&gt;In this blog post we explored alternative workflows which can replace the concept of a traditional Bastion Host accessed by SSH or RDP. AWS provides customers with alternatives so that you can choose the one that best fits your use-case.&lt;/p&gt;

&lt;p&gt;| &lt;strong&gt;Alternative&lt;/strong&gt; | &lt;strong&gt;Good&lt;/strong&gt; | &lt;strong&gt;Not so good&lt;/strong&gt; | &lt;strong&gt;Pricing&lt;/strong&gt; |&lt;br&gt;
| &lt;strong&gt;AWS Systems Manager – Session Manager&lt;/strong&gt; | Provides easy access to any compute resource from the AWS Console or the AWS CLI with federation/AWS IAM Identity Center support.  &lt;/p&gt;

&lt;p&gt;Supports port forwarding and local GUI clients.  &lt;/p&gt;

&lt;p&gt;Full integration with Cloudtrail and CloudWatch Logs for auditing and activity tracking | All prerequisites for SSM Managed Instances may be seen as a hurdle, but can be solved with IaC. | No additional charges for accessing Amazon EC2 instances.  &lt;/p&gt;

&lt;p&gt;The advanced on-premises instance tier is required for using Session Manager to interactively access on-premises instances.  |&lt;br&gt;
| &lt;strong&gt;AWS CloudShell VPC Environments&lt;/strong&gt; | Available anywhere in the AWS Console.  &lt;/p&gt;

&lt;p&gt;Does not require extensive configuration.  &lt;/p&gt;

&lt;p&gt;Easy to use for quick commands or lookups.  &lt;/p&gt;

&lt;p&gt;Ephemeral storage, auto-cleanup after session inactivity.&lt;br&gt;&lt;br&gt;
 | Ephemeral storage, auto-cleanup after session inactivity.  &lt;/p&gt;

&lt;p&gt;Due to possible session timeout issues consider other options for long running commands, database imports/exports etc.  &lt;/p&gt;

&lt;p&gt;Audit and activity logging capabilities not matching SSM Session Manager. | No additional charges, minimum fees nor commitments. You only pay for other AWS resources you use with CloudShell to create and run your applications. |&lt;br&gt;
| &lt;strong&gt;AWS Cloud9&lt;/strong&gt; | Appealing if you’re also working on code development (application/IaC).&lt;br&gt;&lt;br&gt;
Same IDE experience regardless of client device.  &lt;/p&gt;

&lt;p&gt;Terraform/Cloudformation deployments can be triggered triggered from the same terminal.  &lt;/p&gt;

&lt;p&gt;Data is persisted on EBS until environment termination. | Session/permission management can be cumbersome.  &lt;/p&gt;

&lt;p&gt;Audit and activity logging capabilities not matching SSM Session Manager. | No additional charges, minimum fees nor commitments for the service itself. You pay only for the compute (EC2) and storage resources (EBS) for the environment.   &lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;&lt;br&gt;
t2.micro Linux instance at $0.0116/hour x 90 total hours used per month = $1.05&lt;br&gt;&lt;br&gt;
$0.10 per GB-month of provisioned storage x 10-GB storage volume = $1.00  &lt;/p&gt;

&lt;p&gt;Total monthly fees: $2.05 |&lt;/p&gt;

&lt;p&gt;My personal preference is to pivot to immutability with containers and automation, but AWS Systems Manager – Session Manager would be the most viable alternative workflow for any remaining EC2 based workloads.&lt;/p&gt;

&lt;p&gt;Instead of querying the database directly for troubleshooting or support requests, &lt;a href="https://docs.aws.amazon.com/wellarchitected/2023-10-03/framework/sec_protect_data_rest_use_people_away.html" rel="noopener noreferrer"&gt;develop a support microservice or dashboard for the purpose&lt;/a&gt;. It could be as simple as SELECT * from relevant tables which returns relevant data in JSON format, with standard employee authentication and authorization mechanisms built in.&lt;/p&gt;

&lt;p&gt;Full Terraform sample code is available at &lt;a href="https://github.com/haakond/terraform-aws-bastion-host-alternatives" rel="noopener noreferrer"&gt;https://github.com/haakond/terraform-aws-bastion-host-alternatives&lt;/a&gt;, feel free to grab anything you need.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/wellarchitected/2023-10-03/framework/sec_protect_data_rest_use_people_away.html" rel="noopener noreferrer"&gt;SEC08-BP05 Use mechanisms to keep people away from data&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager.html" rel="noopener noreferrer"&gt;AWS Systems Manager Session Manager&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/patch-manager.html" rel="noopener noreferrer"&gt;AWS Systems Manager Patch Manager&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/run-command.html" rel="noopener noreferrer"&gt;AWS Systems Manager Run Command&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with.html" rel="noopener noreferrer"&gt;Working with Session Manager&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/cloudshell/latest/userguide/using-cshell-in-vpc.html" rel="noopener noreferrer"&gt;AWS CloudShell – Using AWS CloudShell in Amazon VPC&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/cloud9/latest/user-guide/welcome.html" rel="noopener noreferrer"&gt;AWS Cloud9 User Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://martinfowler.com/bliki/ImmutableServer.html" rel="noopener noreferrer"&gt;Martin Fowler – Immutable server&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The post &lt;a href="https://hedrange.com/2024/07/03/bye-bye-bastion/" rel="noopener noreferrer"&gt;Bye bye Bastion!&lt;/a&gt; first appeared on &lt;a href="https://hedrange.com" rel="noopener noreferrer"&gt;Håkon Eriksen Drange&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>articles</category>
      <category>bastion</category>
      <category>security</category>
      <category>aws</category>
    </item>
  </channel>
</rss>
