<?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: Matt Lewis</title>
    <description>The latest articles on DEV Community by Matt Lewis (@mlewis7127).</description>
    <link>https://dev.to/mlewis7127</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%2F289832%2F315e716e-c246-4b21-9838-c216c0729c6b.png</url>
      <title>DEV Community: Matt Lewis</title>
      <link>https://dev.to/mlewis7127</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mlewis7127"/>
    <language>en</language>
    <item>
      <title>Strands Agents + AgentCore Runtime - a perfect match</title>
      <dc:creator>Matt Lewis</dc:creator>
      <pubDate>Wed, 20 May 2026 21:17:54 +0000</pubDate>
      <link>https://dev.to/aws-heroes/strands-agents-agentcore-runtime-a-perfect-match-3a51</link>
      <guid>https://dev.to/aws-heroes/strands-agents-agentcore-runtime-a-perfect-match-3a51</guid>
      <description>&lt;p&gt;This is the third in a series of posts documenting the architecture, implementation, and lessons learned from building the AWS Briefing Agent - a personalised AWS assistant deployed on &lt;code&gt;Amazon Bedrock AgentCore Runtime&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Part 1: &lt;a href="https://dev.to/aws-heroes/building-a-full-stack-ai-agent-on-amazon-bedrock-agentcore-2p"&gt;Building a Full-Stack AI Agent on Bedrock AgentCore&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 2: &lt;a href="https://dev.to/aws-heroes/data-ingestion-rss-feeds-knowledge-base-s3-vectors-and-metadata-filtering-4n8m"&gt;Data Ingestion: RSS Feeds, Knowledge Base, S3 Vectors, and Metadata Filtering&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 3: Strands Agents + AgentCore Runtime - a perfect match&lt;/li&gt;
&lt;li&gt;Part 4: Adding Memory to the Agent&lt;/li&gt;
&lt;li&gt;Part 5: Experimenting with API Gateway&lt;/li&gt;
&lt;li&gt;Part 6: Observability and Evaluations&lt;/li&gt;
&lt;li&gt;Part 7: Third Party Integrations - Identity, Gateway and Slack Notifications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The initial implementation of the AWS Briefing Agent called the &lt;code&gt;AWS News Feed&lt;/code&gt; RSS feed on every invocation. After setting up an &lt;code&gt;Amazon Bedrock Knowledge Base&lt;/code&gt;, the next step was to refactor the code to take advantage of an agentic framework. The decision was made to adopt &lt;code&gt;Strands Agents&lt;/code&gt; SDK as an open source SDK that helps you build and run AI agents in just a few lines of code. In our case, switching to the Knowledge Base and adopting &lt;code&gt;Strands Agents&lt;/code&gt; SDK helped us to reduce the number of lines of code in our implementation logic by 75%.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Strands Agents SDK
&lt;/h2&gt;

&lt;p&gt;The core of the &lt;code&gt;Strands Agents&lt;/code&gt; code is straightforward and shown in the code snippet below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BedrockModel&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands.agent.conversation_manager&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SlidingWindowConversationManager&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands_tools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;retrieve&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;agent.tools.slack_formatter.tool&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;format_slack_message&lt;/span&gt;

&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BedrockModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;guardrail_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;GUARDRAIL_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;guardrail_version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;GUARDRAIL_VERSION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;guardrail_trace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;enabled&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;system_prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;_load_system_prompt&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;retrieve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;format_slack_message&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;gateway_tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;session_manager&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;session_manager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;conversation_manager&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;SlidingWindowConversationManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;window_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;should_truncate_results&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;per_turn&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="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;callback_handler&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We start by importing a number of classes and functions from two packages (&lt;code&gt;strands-agents&lt;/code&gt; and &lt;code&gt;strands-agents-tools&lt;/code&gt;) and one local module. &lt;code&gt;Agent&lt;/code&gt; is the core class for the agent itself, &lt;code&gt;BedrockModel&lt;/code&gt; is the model provider, &lt;code&gt;SlidingWindowConversationManager&lt;/code&gt; controls how conversation history is trimmed, and &lt;code&gt;retrieve&lt;/code&gt; is a pre-built tool that is used to query a Bedrock Knowledge Base. The &lt;code&gt;format_slack_message&lt;/code&gt; is a local custom tool within this project - a Python function decorated with the &lt;code&gt;@tool&lt;/code&gt; annotation.&lt;/p&gt;

&lt;p&gt;We instantiate the &lt;code&gt;BedrockModel()&lt;/code&gt; without specifying a model_id. At this point, Strands uses its default model, which is current Claude Sonnet on Bedrock. We include details of a Bedrock Guardrail when we instantiate the model, purely to demonstrate the use of guardrails which we cover this later in the blog post.&lt;/p&gt;

&lt;p&gt;Finally, we create the agent by wiring together its core components.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploy to Amazon Bedrock AgentCore Runtime
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;AgentCore Runtime&lt;/code&gt; Python SDK provides a lightweight wrapper that helps to deploy your agent function as HTTP services&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;# Import the runtime
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;bedrock_agentcore.runtime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BedrockAgentCoreApp&lt;/span&gt;

&lt;span class="c1"&gt;# Initialise the app
&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BedrockAgentCoreApp&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Decorate the function
&lt;/span&gt;&lt;span class="nd"&gt;@app.entrypoint&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Entry point for AgentCore Runtime.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payload&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;prompt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="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;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;BedrockAgentCoreApp&lt;/code&gt; wraps your function in an HTTP server that listens om port 8080 with two endpoints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/invocations&lt;/code&gt; - a POST endpoint for agent interactions. This gets invoked when customers call the &lt;code&gt;InvokeAgentRuntime&lt;/code&gt; action with the payload in JSON format&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/ping&lt;/code&gt; - a GET endpoint for health checks to verify your agent is operational and ready to handle requests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;@app.entrypoint&lt;/code&gt; decorator registers your invoke function as the handler for incoming requests. When AgentCore Runtime receives a request, it deserialises the JSON body into payload, provides a context object (with session_id, request_headers, etc.), calls your function, and serialises the returned dict back as the HTTP response.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using the Container Build
&lt;/h2&gt;

&lt;p&gt;When using the &lt;code&gt;@aws/agentcore&lt;/code&gt; CLI and running &lt;code&gt;agentcore deploy&lt;/code&gt;, the CLI needs to turn the Python source code into a runnable container image on &lt;code&gt;AgentCore Runtime&lt;/code&gt;. This is controlled by the &lt;code&gt;build&lt;/code&gt; field in the &lt;code&gt;agentcore.json&lt;/code&gt; file. The default setting is &lt;code&gt;CodeZip&lt;/code&gt;, in which the CLI zips up the Python source code, uploads it, and AgentCore resolves dependencies using &lt;code&gt;uv --no-build&lt;/code&gt;. This is fast but has a hard constraint, as every dependency must have a pre-built wheel. In our code, we have a package that only ships source distributions, which required us to switch to the &lt;code&gt;Container&lt;/code&gt; build setting. This also makes our build more production-ready.&lt;/p&gt;

&lt;p&gt;When you run &lt;code&gt;agentcore deploy&lt;/code&gt; with the &lt;code&gt;Container&lt;/code&gt; build type, the CLI synthesis a CloudFormation stack that includes a CodeBuild project, an ECR repository, the &lt;code&gt;AgentCore Runtime&lt;/code&gt; resource, and IAM roles. The CLI packages the &lt;code&gt;codeLocation&lt;/code&gt; directory (agent/) and uploads it to S3 as the CodeBuild source artefact. CodeBuild pulls the provided Dockerfile and builds the container image. You can see all the steps in the CodeBuild project 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%2Fuhlvxc1orm6y5bh2x0sz.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%2Fuhlvxc1orm6y5bh2x0sz.png" alt="CodeBuild Project" width="800" height="366"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After the image builds successfully, CodeBuild tags it and pushes it to the ECR repository as shown 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%2Fwnthqxhirsjsm1g30ktf.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%2Fwnthqxhirsjsm1g30ktf.png" alt="Amazon ECR Repository" width="799" height="419"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The stack updates the Runtime resource to point at the new ECR image URI. AgentCore pulls the image from ECR the next time it starts a container for an invocation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Built-In Conversation Managers
&lt;/h2&gt;

&lt;p&gt;In the &lt;code&gt;Strands Agents&lt;/code&gt; SDK, the user messages and agent responses are all added to the context. As the conversation grows within a session, this starting having a material impact on response times. We modified the default &lt;code&gt;SlidingWindowConversationManager&lt;/code&gt; manager:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reducing the &lt;code&gt;windowSize&lt;/code&gt; from the default of 40 to 20. This sets the maximum number of messages to keep&lt;/li&gt;
&lt;li&gt;setting the &lt;code&gt;per_turn&lt;/code&gt; parameter to false. This runs the sliding window before every model call within the same invocation, rather than waiting until after the agent loop completes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This reduced the average response time from around 80 seconds down to 15 seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding Bedrock Guardrails
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Amazon Bedrock Guardrails&lt;/code&gt; are designed to help you safely build and deploy responsible generative AI applications with confidence. We decided to include a guardrail in the architecture, to understand where it fits in and what it can provide.&lt;/p&gt;

&lt;p&gt;The guardrail itself was defined in CDK with content filters (sexual, violence, hate, insults, misconduct and prompt attack), a topic policy (deny off-topic sports questions), and a managed profanity word list:&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;# ----------------------------------------------------------------
# Bedrock Guardrail — content safety for the agent
# ----------------------------------------------------------------
&lt;/span&gt;&lt;span class="n"&gt;guardrail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bedrock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CfnGuardrail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BriefingAgentGuardrail&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;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;briefing-agent-guardrail&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content safety guardrail for the AWS Briefing Agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;blocked_input_messaging&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;I&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;m sorry, I can&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t process that request. Please rephrase your question about AWS announcements.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;blocked_outputs_messaging&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;I&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;m sorry, I can&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t provide that response. Let me try a different approach.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;content_policy_config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;bedrock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CfnGuardrail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ContentPolicyConfigProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;filters_config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="n"&gt;bedrock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CfnGuardrail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ContentFilterConfigProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SEXUAL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;input_strength&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HIGH&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;output_strength&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HIGH&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;bedrock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CfnGuardrail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ContentFilterConfigProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;VIOLENCE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;input_strength&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HIGH&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;output_strength&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HIGH&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="c1"&gt;# HATE, INSULTS, MISCONDUCT, PROMPT_ATTACK
&lt;/span&gt;        &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;topic_policy_config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;bedrock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CfnGuardrail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TopicPolicyConfigProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;topics_config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="n"&gt;bedrock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CfnGuardrail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TopicConfigProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;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;Sports&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;definition&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Questions about sports scores, match results, player transfers, league standings, fixtures, or any sporting events.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DENY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;word_policy_config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;bedrock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CfnGuardrail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;WordPolicyConfigProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;managed_word_lists_config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="n"&gt;bedrock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CfnGuardrail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ManagedWordsConfigProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PROFANITY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the agent is invoked, the request first reaches the AgentCore Runtime and runs the handler code first. The guardrail itself is only applied when the handler makes the Bedrock inference call. Bedrock evaluates the input before running the model inference, and then inspects the output before returning it. We did encounter some interesting behaviour when implementing the guardrail.&lt;/p&gt;

&lt;h3&gt;
  
  
  IAM Permission Gap
&lt;/h3&gt;

&lt;p&gt;The first invocation after adding the guardrail failed with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;AccessDeniedException: User is not authorized to perform: bedrock:ApplyGuardrail
on resource: arn:aws:bedrock:eu-west-1.xxx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AgentCore execution role (auto-created by the @aws/agentcore-cdk construct) includes &lt;code&gt;bedrock:InvokeModel&lt;/code&gt; and &lt;code&gt;bedrock:InvokeModelWithResponseStream&lt;/code&gt;, but not &lt;code&gt;bedrock:ApplyGuardrail&lt;/code&gt;. The construct doesn’t know about guardrails — they’re a Bedrock feature, not an AgentCore feature. We ended up having to use the &lt;code&gt;aws iam put-role-policy&lt;/code&gt; CLI command to add the missing permission&lt;/p&gt;

&lt;h3&gt;
  
  
  Topic policies can false-positive on legitimate queries
&lt;/h3&gt;

&lt;p&gt;The initial topic policy denied "questions not related to AWS services, cloud computing, or technology". The intention was that it would be easy to demonstrate, and would ensure that the user input was relevant. However, when the user asked questions such as "what are the top announcements today", the classifier ended up deciding this was a blocked topic. In the end, to demonstrate how topic policies work, we changed it to explicitly deny sporting questions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Guardrail versions can be deleted by CDK updates
&lt;/h3&gt;

&lt;p&gt;When we updated the topic policy, we changed the version description for the guardrail. The CDK stack updated the guardrail version resource, so that CloudFormation deleted version 1 and created version 2. Unfortunately, the version number is also defined in the &lt;code&gt;agentcore.json&lt;/code&gt; file. This meant that the AgentCore Runtime container still had version 1 baked into its environment, which meant calls now failed with the following exception:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ValidationException: The guardrail identifier or version provided &lt;span class="k"&gt;in &lt;/span&gt;the request does not exist.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the end it was a case of having to update the version number in &lt;code&gt;agentcore.json&lt;/code&gt;, redeploy the agent, and start a new session.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>architecture</category>
      <category>aws</category>
    </item>
    <item>
      <title>Data Ingestion: RSS Feeds, Knowledge Base, S3 Vectors, and Metadata Filtering</title>
      <dc:creator>Matt Lewis</dc:creator>
      <pubDate>Wed, 20 May 2026 21:16:06 +0000</pubDate>
      <link>https://dev.to/aws-heroes/data-ingestion-rss-feeds-knowledge-base-s3-vectors-and-metadata-filtering-4n8m</link>
      <guid>https://dev.to/aws-heroes/data-ingestion-rss-feeds-knowledge-base-s3-vectors-and-metadata-filtering-4n8m</guid>
      <description>&lt;p&gt;This is the second in a series of posts documenting the architecture, implementation, and lessons learned from building the AWS Briefing Agent - a personalised AWS assistant deployed on &lt;code&gt;Amazon Bedrock AgentCore Runtime&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Part 1: &lt;a href="https://dev.to/aws-heroes/building-a-full-stack-ai-agent-on-amazon-bedrock-agentcore-2p"&gt;Building a Full-Stack AI Agent on Bedrock AgentCore&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 2: Data Ingestion: RSS Feeds, Knowledge Base, S3 Vectors, and Metadata Filtering&lt;/li&gt;
&lt;li&gt;Part 3: &lt;a href="https://dev.to/aws-heroes/strands-agents-agentcore-runtime-a-perfect-match-3a51"&gt;Strands Agents + AgentCore Runtime - a perfect match&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 4: Adding Memory to the Agent&lt;/li&gt;
&lt;li&gt;Part 5: Experimenting with API Gateway&lt;/li&gt;
&lt;li&gt;Part 6: Observability and Evaluations&lt;/li&gt;
&lt;li&gt;Part 7: Third Party Integrations - Identity, Gateway and Slack Notifications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When I started building the AWS Briefing Agent, the first version queried the &lt;code&gt;AWS What's New&lt;/code&gt; RSS feed on every invocation. This worked in terms of showing the agent could return tailored information back to the client. However, it was costly and wasteful, with the same data fetched repeatedly, which added latency to every invocation. The RSS feed also only covers recent information, and it was likely we would want to start searching for releases that had been launched in the past 6 months or more. The next step therefore, was to separate the retrieval by the agent from the ingestion. &lt;/p&gt;

&lt;h2&gt;
  
  
  Amazon Bedrock Knowledge Base
&lt;/h2&gt;

&lt;p&gt;One of the key design goals was to allow the agent to match a natural language query "what's new in Bedrock this week?" against a large corpus of documents to return the most semantically similar results. This is where &lt;code&gt;Amazon Bedrock Knowledge Base&lt;/code&gt; comes into its own. It allows the agent to use RAG (Retrieval-Augmented Generation). By querying the Knowledge Base, we can retrieve relevant documents at query time, and then inject them into the prompt as context. The LLM then generates a response from this retrieved information which we know to be factual.&lt;/p&gt;

&lt;p&gt;The python CDK code that creates the Knowledge Base is shown below:&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;knowledge_base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bedrock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CfnKnowledgeBase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AnnouncementKnowledgeBase&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;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;aws-briefing-agent-announcements&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;
    &lt;span class="n"&gt;knowledge_base_configuration&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;bedrock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CfnKnowledgeBase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;KnowledgeBaseConfigurationProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;VECTOR&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;vector_knowledge_base_configuration&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;bedrock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CfnKnowledgeBase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;VectorKnowledgeBaseConfigurationProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;embedding_model_arn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;arn:aws:bedrock:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;::foundation-model/amazon.titan-embed-text-v2:0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;storage_configuration&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;bedrock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CfnKnowledgeBase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;StorageConfigurationProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;S3_VECTORS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;s3_vectors_configuration&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;bedrock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CfnKnowledgeBase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;S3VectorsConfigurationProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;index_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;announcements&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;vector_bucket_arn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;arn:aws:s3vectors:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="si"&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:bucket/briefing-agent-vectors&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This declares the embeddings model to be used as &lt;code&gt;amazon.titan-embed-text-v2:0&lt;/code&gt; and the vector store as being of type &lt;code&gt;S3_VECTORS&lt;/code&gt;. There is no code required to handle aspects such as embeddings. Instead, Bedrock manages all of this for us.&lt;/p&gt;

&lt;h2&gt;
  
  
  Amazon S3 Vectors
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Amazon Bedrock Knowledge Bases&lt;/code&gt; support several vector stores. A vector store is the retrieval engine that makes RAG work. It stores documents as numerical embeddings (vectors) that are generated by an embeddings model. At query time, the user's question is embedded, and the vector store finds documents whose embeddings are closest in meaning. &lt;/p&gt;

&lt;p&gt;The prototype uses &lt;code&gt;Amazon S3 Vectors&lt;/code&gt; as the underlying vector store. &lt;code&gt;S3 Vectors&lt;/code&gt; provides cost-effective, elastic, and durable vector storage at up to 90% lower costs for uploading, storing, and querying vectors than alternatives such as &lt;code&gt;OpenSearch Serverless&lt;/code&gt;. There is no infrastructure to manage, and it still provides a sub-second query latency which is acceptable for this use case.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scheduling the Ingestion
&lt;/h2&gt;

&lt;p&gt;The ingestion pipeline is run every 6 hours using &lt;code&gt;Amazon EventBridge Scheduler&lt;/code&gt;. This service provides capabilities such as built-in retry policies, time zone support, and dead-letter queues. The schedule triggers an &lt;code&gt;AWS Lambda&lt;/code&gt; function that carries out the required processing. This includes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Lists existing document hashes in S3&lt;/li&gt;
&lt;li&gt;Fetches the AWS What’s New RSS feed (~100 announcements)&lt;/li&gt;
&lt;li&gt;Fetches 13 AWS blog RSS feeds (aws, machine-learning, compute, security,
database, containers, devops, networking, storage, infrastructure-and-automation,
developer, big-data, iot)&lt;/li&gt;
&lt;li&gt;Fetches the AWS Security Bulletins RSS feed&lt;/li&gt;
&lt;li&gt;For each new blog post, fetches the canonical URL and extracts the full article body
using a stdlib HTML parser&lt;/li&gt;
&lt;li&gt;Parses publication dates into YYYYMMDD integers&lt;/li&gt;
&lt;li&gt;Writes .txt and .metadata.json files per new item to S3&lt;/li&gt;
&lt;li&gt;Triggers a Bedrock KB ingestion job&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Deduplication and Incremental Writes
&lt;/h2&gt;

&lt;p&gt;When the ingestion pipeline runs, most of the content in the various RSS feeds is not new. It was important to find a way to prevent re-fetching and re-writing hundreds of announcements every 6 hours.&lt;/p&gt;

&lt;p&gt;To support this, we created an MD5 hash of the blog posts URL, truncated to 12 hex characters. This hash is used as the S3 filename. The sample code snippet is shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;write_to_s3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;existing_keys&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;existing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;existing_keys&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nf"&gt;set&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;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;url_hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;md5&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;link&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()[:&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;url_hash&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt; &lt;span class="c1"&gt;# Already in S3, skip
&lt;/span&gt;        &lt;span class="c1"&gt;# ... write doc + metadata files
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At startup, &lt;code&gt;get_existing_keys()&lt;/code&gt; lists all the &lt;code&gt;.txt&lt;/code&gt; files in S3 and extracts the hash from each filename into a set. When processing the blog posts, the Lambda functions computes the URL hash and checks to see if it is already in the set. If it already exists, then it has been ingested in a previous run, and there is no need to re-fetch the page. If the hash does not exist, then the function fetches the page, extracts the content, and writes to S3. The hash gives a stable, deterministic filename derived from the URL. The same URL always produces the same hash.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chunking Strategy
&lt;/h2&gt;

&lt;p&gt;The chunking strategy is set on the Data Source resource in the CDK stack as shown below:&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;data_source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bedrock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CfnDataSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AnnouncementDataSource&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;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;aws-announcements-s3&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;knowledge_base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attr_knowledge_base_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;data_source_configuration&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;bedrock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CfnDataSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataSourceConfigurationProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;S3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;s3_configuration&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;bedrock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CfnDataSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;S3DataSourceConfigurationProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;bucket_arn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bucket_arn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;vector_ingestion_configuration&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;bedrock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CfnDataSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;VectorIngestionConfigurationProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;chunking_configuration&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;bedrock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CfnDataSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ChunkingConfigurationProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;chunking_strategy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SEMANTIC&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;semantic_chunking_configuration&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;bedrock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CfnDataSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SemanticChunkingConfigurationProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;breakpoint_percentile_threshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;92&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;buffer_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;max_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We utilise a &lt;code&gt;SEMANTIC&lt;/code&gt; chunking strategy. This uses the embedding model itself to decide where to split. The following three parameters control this behaviour:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;breakpoint_percentile_threshold=92&lt;/strong&gt; - controls the percentile threshold that will result in a split. A higher threshold requires sentences to be more distinguishable to split the document into different chunks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;max_tokens=600&lt;/strong&gt; - the maximum number of tokens that should be included in a single chunk, while honoring sentence boundaries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;buffer_size=1&lt;/strong&gt; - for a given sentence, the buffer size defines the number of surrounding sentences to be added for embeddings creation. A larger buffer size might capture more context but can also introduce noise, while a smaller buffer size might miss important context but ensures more precise chunking.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Filtering by Date
&lt;/h2&gt;

&lt;p&gt;One of the goals in writing the agent was that a user could ask to constrain information by how recent it is e.g. "what is new in the past 7 days?". &lt;/p&gt;

&lt;p&gt;To help achieve this, at ingestion time for each document, we create an associated &lt;code&gt;metadata.json&lt;/code&gt; sidecar file that attaches structured, filterable attributes to a document so the agent can narrow search results without relying only on semantic similarity. An example companion file is shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"metadataAttributes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"published_date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20260415&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"service"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"amazon-bedrock"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"artificial-intelligence"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"source_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"announcement"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During the Knowledge Base sync, Bedrock reads this sidecar and attaches those attributes to every vector chunk generated from that document. At query time, the agent can combine semantic search with metadata filters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"What's new in Bedrock this week?" → vector similarity for "Bedrock" + greaterThanOrEquals filter on published_date&lt;/li&gt;
&lt;li&gt;"Show me security bulletins" → vector similarity + equals filter on source_type: "security-bulletin"&lt;/li&gt;
&lt;li&gt;"Lambda announcements from the last month" → vector similarity + filters on both service and published_date&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without the metadata file, the agent would get the most semantically similar results regardless of date or service — so a question about "this week" might return announcements from 3 months ago that happen to be textually similar. The metadata filters let the agent constrain results to the correct time window or service before ranking by relevance.&lt;/p&gt;

&lt;p&gt;The naming convention (.metadata.json) is a Bedrock KB convention — it automatically associates the sidecar with its parent document during ingestion. No code links them; the filename pattern is enough.&lt;/p&gt;

&lt;p&gt;Bedrock Knowledge Base metadata supports four types: STRING, NUMBER, BOOLEAN and STRING_LIST. There is no native data type. The comparison operators (greaterThan, greaterThanOrEquals, lessThan, lessThanOrEquals) only work with NUMBER. Our original implementation stored &lt;code&gt;published_date&lt;/code&gt; as a string ("2026-05-14"). When the agent tried to filter, we got back the following exception:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ValidationException: The filter value &lt;span class="nb"&gt;type &lt;/span&gt;provided isn&lt;span class="s1"&gt;'t supported
for the given operation: GREATER_THAN_OR_EQUALS
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fix was to store dates as &lt;code&gt;YYYYMMDD&lt;/code&gt; numbers (so using "20260514" instead of "2026-05-14"). We also inject today's date into the system prompt at runtime so the LLM can easily calculate relative dates.&lt;/p&gt;

&lt;p&gt;Note that &lt;code&gt;Amazon S3 Vectors&lt;/code&gt; has a strict 2 KB limit on filterable metadata per vector. We found the Bedrock Knowledge Base internal metadata keys (&lt;code&gt;AMAZON_BEDROCK_TEXT&lt;/code&gt; and &lt;code&gt;AMAZON_BEDROCK_METADATA&lt;/code&gt;) were set as filterable by default, which caused frequent &lt;code&gt;ValidationException&lt;/code&gt; errors. The fix was mark both of these keys as non-filterable when creating the vector index:&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;vector_index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s3vectors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CfnIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AnnouncementVectorIndex&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;index_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;announcements&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;vector_bucket_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;vector_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vector_bucket_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;dimension&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# Titan Embed Text v2
&lt;/span&gt;    &lt;span class="n"&gt;distance_metric&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cosine&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;data_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;float32&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;metadata_configuration&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;s3vectors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CfnIndex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;MetadataConfigurationProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;non_filterable_metadata_keys&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;AMAZON_BEDROCK_TEXT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AMAZON_BEDROCK_METADATA&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This meant the only filterable metadata is contained in the &lt;code&gt;.metadata.json&lt;/code&gt; fields, which are the only fields we filter on.&lt;/p&gt;

&lt;p&gt;The next post covers how we used an agentic framework (Strands Agents SDK) in combination with AgentCore to really start bringing the briefing agent to life.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>aws</category>
      <category>rag</category>
    </item>
    <item>
      <title>Building a Full-Stack AI Agent on Amazon Bedrock AgentCore</title>
      <dc:creator>Matt Lewis</dc:creator>
      <pubDate>Wed, 20 May 2026 21:14:23 +0000</pubDate>
      <link>https://dev.to/aws-heroes/building-a-full-stack-ai-agent-on-amazon-bedrock-agentcore-2p</link>
      <guid>https://dev.to/aws-heroes/building-a-full-stack-ai-agent-on-amazon-bedrock-agentcore-2p</guid>
      <description>&lt;p&gt;This is the first in a series of posts documenting the architecture, implementation, and lessons learned from building the AWS Briefing Agent - a personalised AWS assistant deployed on &lt;code&gt;Amazon Bedrock AgentCore Runtime&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Part 1: Building a Full-Stack AI Agent on Bedrock AgentCore&lt;/li&gt;
&lt;li&gt;Part 2: &lt;a href="https://dev.to/aws-heroes/data-ingestion-rss-feeds-knowledge-base-s3-vectors-and-metadata-filtering-4n8m"&gt;Data Ingestion: RSS Feeds, Knowledge Base, S3 Vectors, and Metadata Filtering&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 3: &lt;a href="https://dev.to/aws-heroes/building-a-full-stack-ai-agent-on-amazon-bedrock-agentcore-2p"&gt;Strands Agents + AgentCore Runtime - a perfect match&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 4: Adding Memory to the Agent&lt;/li&gt;
&lt;li&gt;Part 5: Experimenting with API Gateway&lt;/li&gt;
&lt;li&gt;Part 6: Observability and Evaluations&lt;/li&gt;
&lt;li&gt;Part 7: Third Party Integrations - Identity, Gateway and Slack Notifications&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why build an agent?
&lt;/h2&gt;

&lt;p&gt;The last few years have seen a rapid shift from Generative to Agentic AI. Most of us will remember our first experience with ChatGPT where we entered a prompt and got a response back. This was impressive at the time, but was reliant on a user typing a prompt and reacting to the response. We then saw the emergence of early AI agents that could break down tasks into smaller steps and execute them independently. Over the past year, this has evolved into fully autonomous multi-agent systems capable of completing complex tasks with minimal or even no human supervision. &lt;/p&gt;

&lt;p&gt;This shift is accelerating quickly. Gartner predicts that by 2028, more than a third of all enterprise software apps will include Agentic AI, and at least 15% of day-to-day work decisions will be made autonomously by AI agents. For organisations, the question is no longer whether agents will become part of enterprise systems, but how to build them securely, reliably and operate them at scale. From an AWS perspective, &lt;code&gt;Amazon Bedrock AgentCore&lt;/code&gt; provides a way to help enterprises achieve this goal. &lt;/p&gt;

&lt;p&gt;I decided to build an agent utilising AgentCore and its supporting capabilities and which served a purpose ... helping me keep up to date with all the latest announcements from AWS. This agent brings together Memory, Observability, Gateway, Identity, Evaluations and Registry alongside AgentCore Runtime. It allows the agent to personalise briefings just for me from 13 different RSS feeds including What's New, Blog Posts and Security Bulletins. I can get a daily update, as well as automatically post any briefings I'm really interested in to a Slack channel. And I learnt a lot in the process. This blog series covers my experience in building out this agent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why AgentCore Runtime?
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Amazon Bedrock AgentCore&lt;/code&gt; is an AWS service that has been designed specifically for the task of hosting agents. A common saying I keep on hearing is that &lt;code&gt;Bedrock AgentCore&lt;/code&gt; is to agentic applications what &lt;code&gt;AWS Lambda&lt;/code&gt; is to event driven applications.&lt;/p&gt;

&lt;p&gt;At the heart is &lt;code&gt;AgentCore Runtime&lt;/code&gt;, which provides the secure runtime for executing the agent code. &lt;code&gt;AgentCore Runtime&lt;/code&gt; provides session-based isolation, where every session is assigned a dedicated Firecracker microVM with isolated CPU, memory and filesystem resources (the same lightweight virtualisation technology that underpins &lt;code&gt;AWS Lambda&lt;/code&gt; and &lt;code&gt;AWS Fargate&lt;/code&gt;). When the session finishes, the LLM's state information is copied to long-term memory and the entire microVM is destroyed. There is no shared state between sessions, which prevents any cross-session data leakage.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;AgentCore Runtime&lt;/code&gt; is framework-agnostic and supports all popular frameworks such as Strands Agents, LangGraph and CrewAI. It also works with any LLM, such as models offered by &lt;code&gt;Amazon Bedrock&lt;/code&gt;, &lt;code&gt;Anthropic Claude&lt;/code&gt;, &lt;code&gt;Google Gemini&lt;/code&gt; and &lt;code&gt;OpenAI&lt;/code&gt; or even hosted on-premises. It supports long sessions up to 8 hours, which means it can handle complex multi-step tasks or time-consuming background processes. Unlike traditional compute services that charge for pre-allocated resources, &lt;code&gt;AgentCore Runtime&lt;/code&gt; uses consumption-based pricing where you only pay for active CPU and memory usage. With this, I/O wait and idle time is free, and you're only charged for actual resource consumption calculated at per-second increments. The runtime automatically scales from zero to thousands of concurrent sessions on demand, with no capacity planning needed, and includes reliability features like checkpointing to recover gracefully from interruptions.&lt;/p&gt;

&lt;h2&gt;
  
  
  AWS Briefing Agent Architecture
&lt;/h2&gt;

&lt;p&gt;A high-level architecture overview of the AWS Briefing Agent is shown 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%2Fe9yd8notj7suy0uak6zn.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%2Fe9yd8notj7suy0uak6zn.png" alt="Briefing Agent Architecture" width="800" height="472"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AWS Briefing Agent Client&lt;/strong&gt; is a &lt;code&gt;next.js&lt;/code&gt; static site hosted on AWS Amplify Hosting. It integrates directly with &lt;code&gt;Amazon Cognito&lt;/code&gt; using the &lt;code&gt;amazon-cognito-identity-js&lt;/code&gt; SDK, implementing a full sign-in, sign-up and email verification flow.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AWS Briefing Agent&lt;/strong&gt; itself is a Python application built with the &lt;code&gt;Strands Agents&lt;/code&gt; SDK and deployed to &lt;code&gt;AgentCore Runtime&lt;/code&gt; as a Docker container. The &lt;code&gt;@aws/agentcore&lt;/code&gt; CLI handles the full deployment lifecycle. When you run &lt;code&gt;agentcore deploy&lt;/code&gt;, the CLI triggers &lt;code&gt;AWS CodeBuild&lt;/code&gt; to build the Docker image (ARM64), pushes it to &lt;code&gt;Amazon ECR&lt;/code&gt;, and deploys it to &lt;code&gt;AgentCore Runtime&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AgentCore Memory&lt;/strong&gt; provides persistent user knowledge across sessions using two built-in memory strategies. The SEMANTIC memory strategy extracts factual information and knowledge from conversations that have taken place e.g. that a user works with Lambda and EKS. The USER_PREFERENCE memory strategy identifies and extracts user preferences from conversations e.g. that the user prefers technical deep dives. The agent retrieves relevant memory records at the start of each invocation and injects them as context, enabling personalised briefings from the first message of a new session.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AgentCore Observability&lt;/strong&gt; is used to instrument all Bedrock API calls, tool invocations and memory operations. This is carried out entirely by setting &lt;code&gt;enableOtel: true&lt;/code&gt; in the runtime config and using the opentelemetry-instrument wrapper command. Spans show up in &lt;code&gt;CloudWatch Transaction Search&lt;/code&gt; and the &lt;code&gt;CloudWatch GenAI Observability dashboard&lt;/code&gt; is populated with the sessions and traces, and provides the ability to drill into individual invocations.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AgentCore Evaluations&lt;/strong&gt; is configured to run online quality assessments against agent responses using built-in evaluators for Helpfulness, Goal Success Rate, and Correctness. These are shown in the front-end to give an indication on how well the agent is performing for each user.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Bedrock Knowledge Base&lt;/strong&gt; is created and backed by &lt;code&gt;Amazon S3 Vectors&lt;/code&gt; that stores all announcements, blog posts and security bulletins. An ingestion &lt;code&gt;Lambda&lt;/code&gt; runs every 6 hours that writes each item as a .txt file alongside a metadata.json file to the S3 bucket, before triggering a &lt;code&gt;Knowledge Base&lt;/code&gt; sync. The agent queries the KB via the Strands retrieve tool with metadata filters for date ranges and service names, enabling questions like "what's new in Bedrock this week?"&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AgentCore Gateway&lt;/strong&gt; exposes a managed MCP (Model Context Protocol) endpoint that the agent connects to at runtime for tool discovery. The Slack integration is defined as an OpenAPI spec pointing at the Slack &lt;code&gt;chat.postMessage&lt;/code&gt; API, and is registered as a Gateway target. The agent discovers available tools dynamically via the MCP protocol. The Gateway handles authentication and credential injection for this integration with Slack, attaching the stored bot token as a Bearer header on outbound Slack API calls.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AgentCore Identity&lt;/strong&gt; stores the Slack bot token as an API key credential in its token vault (encrypted at rest via Secrets Manager). When the agent calls the tool to send a briefing to Slack, &lt;code&gt;AgentCore Identity&lt;/code&gt; retrieves the bot token and injects it into the outbound request automatically. The agent code never sees or handles the token directly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AgentCore Registry&lt;/strong&gt; is a governed catalog for agents, MCP servers, tools, skills, and custom resources. Teams can publish resources, control access through approval workflows, and enable both humans and AI agents to discover tools using semantic and keyword search. Once the Slack integration was working, the briefing agent and the Slack tool where registered in the &lt;code&gt;AgentCore Registry&lt;/code&gt;. This makes the tool discoverable by other agents in the organisation.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  AWS Briefing Agent in Action
&lt;/h2&gt;

&lt;p&gt;We create a new user and login to the home screen for the AWS Briefing Agent front end. The first time we use the agent, we are asked to provide information about our interests and the type of briefing style we are interested in. These get added to memory, so that the agent can personalise its responses:&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%2Fc4jrd2jm3fczr4jlpg0j.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%2Fc4jrd2jm3fczr4jlpg0j.png" alt="AWS Briefing Agent Home Page" width="800" height="415"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can provide the details of the services we are most interested to the agent. At this point, the agent will pull back the top announcements that it has retrieved from the Knowledge Base, and display them in a briefing summary.&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%2F76xk7bpnkpqfpcf475kk.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%2F76xk7bpnkpqfpcf475kk.png" alt="AWS Briefing Agent Summary" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We have also integrated with Slack through Gateway. This means we can ask the Briefing Agent to post the details to our Slack channel:&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%2Fjar4d6oelrx7kbmgpx97.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%2Fjar4d6oelrx7kbmgpx97.png" alt="AWS Briefing Agent Send to Slack" width="800" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This means that when we go to our Slack channel, we can see a new message with our briefing, alongside all the links we can click to take us to the original blog posts and announcement articles.&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%2F21vtrzzmyc8ugsi4w153.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%2F21vtrzzmyc8ugsi4w153.png" alt="Slack Message" width="800" height="442"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the next post we cover design decisions made to ingest the data into a Bedrock Knowledge Base to support the agent&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>aws</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Moving from Node Groups to NodePools on Amazon EKS</title>
      <dc:creator>Matt Lewis</dc:creator>
      <pubDate>Sat, 21 Feb 2026 17:38:16 +0000</pubDate>
      <link>https://dev.to/aws-heroes/moving-from-node-groups-to-nodepools-on-amazon-eks-1kc5</link>
      <guid>https://dev.to/aws-heroes/moving-from-node-groups-to-nodepools-on-amazon-eks-1kc5</guid>
      <description>&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;In November 2019, AWS introduced the concept of Amazon EKS Managed Node Groups. With this, Amazon EKS would provision and manage the underlying EC2 instances as worker nodes, as part of an EC2 Auto Scaling Group. You could create, update or terminate a node with a single operation. When updating or terminating a node, EKS would handle these operations gracefully by automatically draining nodes to ensure applications stayed available. Futher enhancements allowed for node configuration and customisation through EC2 Launch Templates and custom AMIs, alongside support for EC2 spot instances.&lt;/p&gt;

&lt;p&gt;However, the modern trend in Kubernetes is moving away from static node groups to dynamic node provisioning with tools like Karpenter for more flexible and cost-effective infrastructure management. With Amazon EKS Auto Mode, the recommendation is no longer to create traditional node groups. Instead, you create a Karpenter NodePool that defines the compute requirements. Amazon EKS Auto Mode provides two built-in node pools - &lt;code&gt;system&lt;/code&gt; and &lt;code&gt;general-purpose&lt;/code&gt; - which you cannot modify, but you can enable or disable. The &lt;code&gt;general-purpose&lt;/code&gt; node pool provides support for launching nodes for general purpose workloads. It supports only &lt;code&gt;amd64&lt;/code&gt; architecture and uses only on-demand EC2 capacity in the &lt;code&gt;C&lt;/code&gt;, &lt;code&gt;M&lt;/code&gt; or &lt;code&gt;R&lt;/code&gt; instance families.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What happens if you want to take advantage of spot instances?&lt;/li&gt;
&lt;li&gt;What happens if you want to take advantage of Graviton?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's show how you can create a node pool to do just that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a Karpenter NodePool
&lt;/h2&gt;

&lt;p&gt;The complete configuration files for this post can be found in the &lt;code&gt;k8s\node-pool&lt;/code&gt; section of the code repository &lt;a href="https://github.com/mlewis7127/amazon-eks-guide-code/tree/main/k8s/node-pool" rel="noopener noreferrer"&gt;here&lt;/a&gt;. We can create it using the following command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; arm-nodepool.yaml
nodepool.karpenter.sh/arm-mixed-capacity created
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The start of the &lt;code&gt;arm-nodepool.yaml&lt;/code&gt; configuration file is as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;karpenter.sh/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NodePool&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;arm-mixed-capacity&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells us we are using the NodePool API with Karpenter. This uses the &lt;code&gt;nodepools.karpenter.sh&lt;/code&gt; CRD which is installed by default with Auto Mode. The &lt;code&gt;spec&lt;/code&gt; element provides the contract with Karpenter. It has the following high-level structure, and we will go through each one in order.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;disruption&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;   &lt;span class="c1"&gt;# when and how nodes can be replaced&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;     &lt;span class="c1"&gt;# what a node looks like&lt;/span&gt;
  &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;       &lt;span class="c1"&gt;# optional safety rails&lt;/span&gt;
  &lt;span class="na"&gt;weight&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;       &lt;span class="c1"&gt;# optional priority&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Disruption
&lt;/h3&gt;

&lt;p&gt;The disruption section describes the ways in which Karpenter can disrupt and replace nodes. This is used when Karpenter wants to remove empty nodes, replace under-utilised nodes with better fitting ones, or shrink the cluster to save money.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="c1"&gt;# Disruption settings for node lifecycle management&lt;/span&gt;
  &lt;span class="na"&gt;disruption&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;consolidationPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;WhenEmptyOrUnderutilized&lt;/span&gt;
    &lt;span class="na"&gt;consolidateAfter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10m&lt;/span&gt;  &lt;span class="c1"&gt;# Wait 10 minutes before consolidating&lt;/span&gt;

    &lt;span class="c1"&gt;# Disruption budgets to control how many nodes can be disrupted&lt;/span&gt;
    &lt;span class="na"&gt;budgets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# During business hours: more conservative&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;nodes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2"&lt;/span&gt;
        &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;9&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;mon-fri"&lt;/span&gt;  &lt;span class="c1"&gt;# 9 AM Mon-Fri&lt;/span&gt;
        &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;8h&lt;/span&gt;
      &lt;span class="c1"&gt;# Outside business hours: more aggressive&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;nodes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10%"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;consolidationPolicy&lt;/code&gt; describes which types of nodes Karpenter should consider for consolidation. There are 2 options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;WhenEmptyOrUnderutilized&lt;/code&gt; - Karpenter will consider all nodes for consolidation and attempt to remove or replace nodes when it discovers that the node is empty or underutilised and could be changed to reduce cost&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;WhenEmpty&lt;/code&gt; - Karpenter will only consider nodes for consolidation that contain no workload pods&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;consolidateAfter&lt;/code&gt; field is the amount of time Karpenter should wait to consolidate a node after a pod has been added or removed from the node. We set this to 10 minutes to make sure the behaviour is not too aggressive, and gives the scheduler time to stabilise.&lt;/p&gt;

&lt;p&gt;Disruption budgets are used to control how many nodes can be disrupted. There are two rules defined in this section. The first rule states that between 09:00 and 17:00 on Monday to Friday, Karpenter may disrupt at most 2 nodes at a time. The second rule states that Karpenter may disrupt up to 10% of all nodes at any time. This will not apply between 09:00-17:00 on Monday to Friday as the first rule is more restrictive and so wins out.&lt;/p&gt;

&lt;h3&gt;
  
  
  Template
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;template&lt;/code&gt; section defines the exact shape, rules and constraints of every node that Karpenter is allowed to create as part of this NodePool.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="c1"&gt;# Node template specification&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# Termination grace period (24 hours)&lt;/span&gt;
      &lt;span class="na"&gt;terminationGracePeriod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;24h&lt;/span&gt;

      &lt;span class="c1"&gt;# Node requirements&lt;/span&gt;
      &lt;span class="na"&gt;requirements&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# ARM architecture&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kubernetes.io/arch&lt;/span&gt;
          &lt;span class="na"&gt;operator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;In&lt;/span&gt;
          &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;arm64"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

        &lt;span class="c1"&gt;# Support spot and on-demand (prefer spot for cost)&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;karpenter.sh/capacity-type&lt;/span&gt;
          &lt;span class="na"&gt;operator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;In&lt;/span&gt;
          &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;spot"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;on-demand"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

        &lt;span class="c1"&gt;# ARM instance types (Graviton) - diverse selection&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node.kubernetes.io/instance-type&lt;/span&gt;
          &lt;span class="na"&gt;operator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;In&lt;/span&gt;
          &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# General purpose (M7g)&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;m7g.medium"&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;m7g.large"&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;m7g.xlarge"&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;m7g.2xlarge"&lt;/span&gt;
            &lt;span class="c1"&gt;# Burstable (T4g) - cost-effective for variable workloads&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;t4g.medium"&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;t4g.large"&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;t4g.xlarge"&lt;/span&gt;
            &lt;span class="c1"&gt;# Compute optimized (C7g)&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;c7g.large"&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;c7g.xlarge"&lt;/span&gt;
            &lt;span class="c1"&gt;# Memory optimized (R7g)&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;r7g.large"&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;r7g.xlarge"&lt;/span&gt;

      &lt;span class="c1"&gt;# Node class reference (Auto Mode creates this automatically)&lt;/span&gt;
      &lt;span class="na"&gt;nodeClassRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;eks.amazonaws.com&lt;/span&gt;
        &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NodeClass&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;


      &lt;span class="c1"&gt;# Taints (optional - for dedicated ARM workloads)&lt;/span&gt;
      &lt;span class="na"&gt;taints&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;arch&lt;/span&gt;
          &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;arm64&lt;/span&gt;
          &lt;span class="na"&gt;effect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NoSchedule&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;terminationGracePeriod&lt;/code&gt; field defines the amount of time that a node can be draining before Karpenter forcibly cleans it up.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;spec.requirements&lt;/code&gt; section provides more details about the nodes that can be created. There are a specified here as an example.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;kubernetes.io/arch&lt;/code&gt; key sets out the architecture for the node. Karpenter supports &lt;code&gt;amd64&lt;/code&gt; and &lt;code&gt;arm64&lt;/code&gt; nodes. This is how we support Graviton.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;karpenter.sh/capacity-type&lt;/code&gt; key is analogous to EC2 puchase options. The &lt;code&gt;general-purpose&lt;/code&gt; NodePool only supports &lt;code&gt;on-demand&lt;/code&gt; as a value, whereas he we specify both &lt;code&gt;spot&lt;/code&gt; and &lt;code&gt;on-demand&lt;/code&gt;. As multiple capacity types are specified, Karpenter will prioritise &lt;code&gt;spot&lt;/code&gt; where available, but fallback to on-demand.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; AWS automatically applies Amazon EC2 Reserved Instance discounts to matching running on-demand EC2 usage, regardless of how these instances were launched. This means that you will get these discounts for instances launched by Karpenter&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There are a number of instance type options&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;key: node.kubernetes.io/instance-type&lt;/li&gt;
&lt;li&gt;key: karpenter.k8s.aws/instance-family&lt;/li&gt;
&lt;li&gt;key: karpenter.k8s.aws/instance-category&lt;/li&gt;
&lt;li&gt;key: karpenter.k8s.aws/instance-generation&lt;/li&gt;
&lt;li&gt;key: karpenter.k8s.aws/instance-capability-flex&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Generally, instance types should be a list and not a single value. Leaving these requirements undefined is recommended, as it maximizes choices for efficiently placing pods.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Each NodePool must reference a NodeClass. A Node Class defines infrastructure-level settings that apply to groups of nodes in your EKS cluster, including network configuration, storage settings, and resource tagging. When you need to customize how EKS Auto Mode provisions and configures EC2 instances beyond the default settings, creating a Node Class gives you precise control over critical infrastructure parameters. For example, you can specify private subnet placement for enhanced security, configure instance ephemeral storage for performance-sensitive workloads, or apply custom tagging for cost allocation. In this case, we just reference the default Auto Mode NodeClass.&lt;/p&gt;

&lt;p&gt;There is also an example shown on how to apply a &lt;code&gt;taint&lt;/code&gt; to a NodePool. When a taint is applied to a NodePool, Karpenter will only place pods on the nodes that explicitly tolerate the taint. In the example, Karpenter will only place a workload on the node that explicitly states that it supports the ARM architecture.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Toleration for the taint (if you added one)&lt;/span&gt;
&lt;span class="na"&gt;tolerations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;arch&lt;/span&gt;
    &lt;span class="s"&gt;operator&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Equal&lt;/span&gt;
    &lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;arm64&lt;/span&gt;
    &lt;span class="s"&gt;effect&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NoSchedule&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;The limits section is used to constrain the total size of the NodePool. The limits that are set prevent Karpenter from creating new instances, once they have been exceeded. This is done to prevent runaway costs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="c1"&gt;# Limits for this node pool&lt;/span&gt;
  &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1000"&lt;/span&gt;
    &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1000Gi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Weight
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;weight&lt;/code&gt; field controls prioritisation when Karpenter has multiple NodePools to choose from for scheduling a pod. When multiple NodePools can satisfy the requirements for a pod, Karpenter will give priority to the NodePool with the highest weight. If the &lt;code&gt;weight&lt;/code&gt; attribute is not specified, it will default to 0.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="c1"&gt;# Weight for prioritization (higher = preferred)&lt;/span&gt;
  &lt;span class="na"&gt;weight&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Karpenter will look to choose the cheapest feasible instance. It prefers NodePools where it can pack the pod more efficiently with other pending pods, and minimise wasted CPU / memory on the node.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Based on the way that Karpenter performs pod batching and bin packing, it is not guaranteed that Karpenter will always choose the highest priority NodePool given specific requirements. For example, if a pod can’t be scheduled with the highest priority NodePool, it will force creation of a node using a lower priority NodePool, allowing other pods from that batch to also schedule on that node. The behavior may also occur if existing capacity is available, as the kube-scheduler will schedule the pods instead of allowing Karpenter to provision a new node.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Targetting the NodePool with a Deployment
&lt;/h2&gt;

&lt;p&gt;In order to test the NodePool and show it working, we created a Deployment, which is a simple Nginx container. It can be deployed using the following command from the code repository.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; arm-deployment.yaml
deployment.apps/arm-app created
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We define a Deployment and give it the name of &lt;code&gt;arm-app&lt;/code&gt;, which is also assigned a label of the same name.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;arm-app&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;arm-app&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next part of the manifest file tells Kubernetes to run 3 copies of the application, and to make sure they are labelled as &lt;code&gt;app=arm-app&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;arm-app&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;arm-app&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The manifest file then defines a &lt;code&gt;nodeSelector&lt;/code&gt; which is a rule that states that these pods can only run on nodes with an architecture type of &lt;code&gt;arm64&lt;/code&gt;. This matches the architecture of our NodePool. Kubernetes will only schedule the Pod onto nodes that match the labels specified.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Node selector for ARM architecture&lt;/span&gt;
&lt;span class="na"&gt;nodeSelector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;kubernetes.io/arch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;arm64&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next part of the manifest file moves onto &lt;code&gt;affinity&lt;/code&gt;. Node affinity functions like the &lt;code&gt;nodeSelector&lt;/code&gt; field but is more expressive and allows you to specify soft rules. In this case, we use &lt;code&gt;preferredDuringSchedulingIgnoredDuringExecution&lt;/code&gt; with a weight of 100 to state that we want the Pod to run on a Spot instance, but if this cannot be scheduled, then it is fine to drop back to on-demand. This means that the Pod will not remain in a pending state if a Spot instance was not available, and so it is considered a soft rule.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Prefer spot instances for cost savings&lt;/span&gt;
&lt;span class="na"&gt;affinity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;nodeAffinity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;preferredDuringSchedulingIgnoredDuringExecution&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;weight&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;
      &lt;span class="na"&gt;preference&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;matchExpressions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;karpenter.sh/capacity-type&lt;/span&gt;
            &lt;span class="na"&gt;operator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;In&lt;/span&gt;
            &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;spot"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, we use the &lt;code&gt;containers&lt;/code&gt; section to say that we want to run a copy of nginx in each Pod, which half a CPU and 512 MB of memory reserved, but this can grow to a whole CPU and 1 GB or memory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx:latest&lt;/span&gt;  &lt;span class="c1"&gt;# Multi-arch image supports ARM64&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http&lt;/span&gt;
    &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;500m&lt;/span&gt;
        &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;512Mi&lt;/span&gt;
      &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1000m&lt;/span&gt;
        &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1Gi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We open up a number of additional terminal windows as we apply the Deployment, to give us more information on what exactly is happening in the background.&lt;/p&gt;

&lt;p&gt;The first command lists all the nodes in the EKS cluster including a column showing their architecture and a column showing their capacity type. We can see that a node is in the Ready state which uses ARM and is a spot instance.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get nodes &lt;span class="nt"&gt;-L&lt;/span&gt; kubernetes.io/arch,karpenter.sh/capacity-type
NAME                  STATUS   ROLES    AGE     VERSION               ARCH    CAPACITY-TYPE
i-0d336c28e588123ae   Ready    &amp;lt;none&amp;gt;   2m22s   v1.34.3-eks-3c60543   arm64   spot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second command lists the &lt;code&gt;NodeClaim&lt;/code&gt; resources. A &lt;code&gt;NodeClaim&lt;/code&gt; is a custom resource created by Karpenter. Here we can see the generated &lt;code&gt;NodeClaim&lt;/code&gt; name is taken from the name of the &lt;code&gt;NodePool&lt;/code&gt; with a random suffix. We can also see it is using spot capacity, and a supported instance family type.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get nodeclaims
NAME                       TYPE         CAPACITY   ZONE         NODE                  READY   AGE
arm-mixed-capacity-zw6vh   m7g.xlarge   spot       eu-west-2a   i-0d336c28e588123ae   True    3m
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next command describes all pods that have the label &lt;code&gt;app=arm-app&lt;/code&gt;. This is the label that gets applied as part of the deployment. It filters the output to show the pod lifecycle events. Again, we can see from this that the pod is running on an ARM-based Graviton spot instance. The event timeline shows the lifecycle involved here. The pod is bound to a compatible node, it then downloads the latest nginx image from the container registry, the container is then created, and finally started.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl describe pod &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;arm-app | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-A&lt;/span&gt; 20 Events

Name:             arm-app-6674bd9849-ld6fm
Namespace:        default
Priority:         0
Service Account:  default
Node:             i-0d336c28e588123ae/10.1.3.225
Start Time:       Tue, 27 Jan 2026 11:42:06 +0000
Labels:           &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;arm-app
                  pod-template-hash&lt;span class="o"&gt;=&lt;/span&gt;6674bd9849
Annotations:      &amp;lt;none&amp;gt;
Status:           Running
IP:               10.1.3.97
&lt;span class="nt"&gt;--&lt;/span&gt;
Events:
  Type    Reason     Age    From               Message
  &lt;span class="nt"&gt;----&lt;/span&gt;    &lt;span class="nt"&gt;------&lt;/span&gt;     &lt;span class="nt"&gt;----&lt;/span&gt;   &lt;span class="nt"&gt;----&lt;/span&gt;               &lt;span class="nt"&gt;-------&lt;/span&gt;
  Normal  Scheduled  2m14s  default-scheduler  Successfully assigned default/arm-app-6674bd9849-ld6fm to i-0d336c28e588123ae
  Normal  Pulling    2m12s  kubelet            spec.containers&lt;span class="o"&gt;{&lt;/span&gt;nginx&lt;span class="o"&gt;}&lt;/span&gt;: Pulling image &lt;span class="s2"&gt;"nginx:latest"&lt;/span&gt;
  Normal  Pulled     2m9s   kubelet            spec.containers&lt;span class="o"&gt;{&lt;/span&gt;nginx&lt;span class="o"&gt;}&lt;/span&gt;: Successfully pulled image &lt;span class="s2"&gt;"nginx:latest"&lt;/span&gt; &lt;span class="k"&gt;in &lt;/span&gt;3.874s &lt;span class="o"&gt;(&lt;/span&gt;3.874s including waiting&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt; Image size: 61200811 bytes.
  Normal  Created    2m8s   kubelet            spec.containers&lt;span class="o"&gt;{&lt;/span&gt;nginx&lt;span class="o"&gt;}&lt;/span&gt;: Created container: nginx
  Normal  Started    2m8s   kubelet            spec.containers&lt;span class="o"&gt;{&lt;/span&gt;nginx&lt;span class="o"&gt;}&lt;/span&gt;: Started container nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We ran a similar command to list all of the pods running, and to show that the 3 replicas as specified in the deployment are running.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get pods &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;arm-app &lt;span class="nt"&gt;-w&lt;/span&gt;
NAME                       READY   STATUS             RESTARTS   AGE
arm-app-6674bd9849-ld6fm   0/1     Pending             0          0s
arm-app-6674bd9849-76wzg   0/1     Pending             0          0s
arm-app-6674bd9849-2vnwx   0/1     Pending             0          0s
arm-app-6674bd9849-ld6fm   0/1     ContainerCreating   0          0s
arm-app-6674bd9849-76wzg   0/1     ContainerCreating   0          0s
arm-app-6674bd9849-2vnwx   0/1     ContainerCreating   0          0s
arm-app-6674bd9849-2vnwx   0/1     Running             0          7s
arm-app-6674bd9849-ld6fm   0/1     Running             0          7s
arm-app-6674bd9849-76wzg   0/1     Running             0          7s
arm-app-6674bd9849-ld6fm   1/1     Running             0          13s
arm-app-6674bd9849-76wzg   1/1     Running             0          13s
arm-app-6674bd9849-2vnwx   1/1     Running             0          13s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>aws</category>
      <category>kubernetes</category>
      <category>beginners</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Get started with the Argo CD EKS Capability</title>
      <dc:creator>Matt Lewis</dc:creator>
      <pubDate>Fri, 23 Jan 2026 14:49:00 +0000</pubDate>
      <link>https://dev.to/aws-heroes/get-started-with-the-argo-cd-eks-capability-36kd</link>
      <guid>https://dev.to/aws-heroes/get-started-with-the-argo-cd-eks-capability-36kd</guid>
      <description>&lt;h2&gt;
  
  
  Argo CD Overview
&lt;/h2&gt;

&lt;p&gt;EKS Capabilities was announced at re:Invent 2025. These are Kubernetes-native platform features managed by AWS, that provide higher-level functionality. This post looks at the Argo CD capability.&lt;/p&gt;

&lt;p&gt;Argo CD is a GitOps based continuous deployment tool. Your git repository becomes the source of truth, and Argo CD ensures that your cluster state matches what you have defined in git. AWS have been consistently guiding their customers towards GitOps for a number of years. AWS describe GitOps as being like a reference implementation of best practice with these 4 characteristics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Desired state expressed declaratively&lt;/li&gt;
&lt;li&gt;Desired state is immutable and versioned&lt;/li&gt;
&lt;li&gt;Desired state is automatically applied from source&lt;/li&gt;
&lt;li&gt;Desired state is continuously reconciled&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Argo CD is used by most of AWS customers practicing GitOps in 2025, and has really emerged as its own de facto standard. More than 45% of Kubernetes end-users reported production or planned production use of Argo CD in the 2024 CNCF survey.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up Argo CD Capability via Console
&lt;/h2&gt;

&lt;p&gt;The quickest way to get up and running with Argo CD is via the console. In the EKS console there is a capabilities tab that shows which managed capabilities are deployed in the cluster and which are available.&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%2Fvo3q86i6hzf5yq0u80y7.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%2Fvo3q86i6hzf5yq0u80y7.png" alt="EKS Capabilities in Console" width="800" height="421"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From here, we can click the &lt;code&gt;Create capabilities&lt;/code&gt; button, and tick the checkbox against Argo CD in the Deployment section, before clicking next.&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%2Fso3pgr5a8nzvmtkcw89t.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%2Fso3pgr5a8nzvmtkcw89t.png" alt="Select Argo CD Capability" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This brings up the page where we configure the selected capabilities. The first thing to do is to either select an existing role for the capability role, or select the button to create a new role.&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%2Fblzexkxuld0jouxfn0ii.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%2Fblzexkxuld0jouxfn0ii.png" alt="Create Argo CD Role" width="800" height="240"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, the Argo CD managed capability integrates with AWS Identity Centre for authentication, and uses RBAC roles for authorization. You select the existing instance of IAM Identity Centre which should be pre-populated when you click the drop down. The next step is to assign RBAC roles. This involves specifying an AWS user or group from AWS Identity Centre and assigning them to an Argo CD RBAC role of "Admin", "Editor" or "Viewer".&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%2Flksxlg5f0lv9wju1u0hr.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%2Flksxlg5f0lv9wju1u0hr.png" alt="Argo CD Authentication Access" width="800" height="241"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At this point, you can review and create the managed capability for Argo CD. To understand this process in more detail, we can step through how to setup the capability using IaC.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up Argo CD Capability via IaC and CLI
&lt;/h2&gt;

&lt;p&gt;The code samples required for this section are contained in the &lt;code&gt;CloudFormation&lt;/code&gt; section of this &lt;a href="https://github.com/mlewis7127/amazon-eks-guide-code" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create an IAM Capability Role
&lt;/h3&gt;

&lt;p&gt;The first step is to create an IAM Capability Role. EKS Capabilities use this role to act on your behalf, running controllers in EKS. EKS Capabilities introduced a new service principle called &lt;code&gt;capabilities.eks.amazonaws.com&lt;/code&gt;. When you create the capability role, you need to ensure the trust policy trusts this new service principle. An example of the required trust policy is shown below which is available in a file named "argocd-trust-policy.json":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Version"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2012-10-17"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Statement"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;
        &lt;span class="pi"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Effect"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Allow"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
            &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Principal"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;
                &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Service"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;capabilities.eks.amazonaws.com"&lt;/span&gt;
            &lt;span class="pi"&gt;},&lt;/span&gt;
            &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Action"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;
                &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sts:AssumeRole"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
                &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sts:TagSession"&lt;/span&gt;
            &lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="pi"&gt;}&lt;/span&gt;
    &lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can create an IAM role with this trust policy using the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws iam create-role &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--role-name&lt;/span&gt; ArgoCDCapabilityRole &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--assume-role-policy-document&lt;/span&gt; file://argocd-trust-policy.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next we need to attach permissions to this Capability IAM Role based on the capability needs and what integrations are required. For example, for Argo CD you may need to give permissions to &lt;code&gt;ecr&lt;/code&gt; or &lt;code&gt;codecommit&lt;/code&gt; or &lt;code&gt;codeconnection&lt;/code&gt;, depending on where your source is coming from. When choosing "Create Argo CD role" through the console, an IAM role with the &lt;code&gt;AWSSecretsManagerClientReadOnlyAccess&lt;/code&gt; managed policy pre-selected is created. This managed policy provides read access to all secrets stored in Secrets Manager in your AWS account and is intended for getting started quickly. You have the flexibility to modify these permissions by unselecting this policy or adding different policies as needed. &lt;/p&gt;

&lt;p&gt;We can this managed policy to the created role by using the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws iam attach-role-policy &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--role-name&lt;/span&gt; ArgoCDCapabilityRole &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--policy-arn&lt;/span&gt; arn:aws:iam::aws:policy/AWSSecretsManagerClientReadOnlyAccess
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can achieve the same in CloudFormation but in a single template. The &lt;code&gt;AWS::IAM::Role&lt;/code&gt; config required is shown below, although we will use this as part of the CloudFormation template that creates the EKS Capability, so we can control it in a single stack.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="c1"&gt;# IAM Capability Role for ArgoCD&lt;/span&gt;
  &lt;span class="na"&gt;ArgoCDCapabilityRole&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::IAM::Role&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;RoleName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RoleName"&lt;/span&gt;
      &lt;span class="na"&gt;AssumeRolePolicyDocument&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2012-10-17'&lt;/span&gt;
        &lt;span class="na"&gt;Statement&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Effect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Allow&lt;/span&gt;
            &lt;span class="na"&gt;Principal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;Service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;capabilities.eks.amazonaws.com&lt;/span&gt;
            &lt;span class="na"&gt;Action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;sts:AssumeRole&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;sts:TagSession&lt;/span&gt;
      &lt;span class="na"&gt;ManagedPolicyArns&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;arn:aws:iam::aws:policy/AWSSecretsManagerClientReadOnlyAccess&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create EKS Capability
&lt;/h3&gt;

&lt;p&gt;Finally, we can create the EKS Capability for Argo CD itself. The Argo CD capability is integrated with AWS Identity Centre (IDC). This ensures that single sign-on is enabled for the fully managed and hosted Argo UI instance and for Argo CLI. For this, you need to ensure that your identity centre configuration is passed into the capability when creating it.&lt;/p&gt;

&lt;p&gt;The Argo CD IDC instance identifies the name of the IAM Identity Center instance that is used by your organization to get permissions for Argo CD to access your EKS cluster. Once the capability has been created, the Argo CD IDC instance cannot be edited.&lt;/p&gt;

&lt;p&gt;We can retrieve the IDC Instance ARN directly from the console, or by running the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws sso-admin list-instances &lt;span class="nt"&gt;--region&lt;/span&gt; eu-west-2 &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'Instances[0].InstanceArn'&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; text
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We need to identify the users or groups that should be assigned to be a particular Argo CD role. The users and groups you identify from your IDC instance defines the permissions that the Argo CD capability has to access your EKS cluster. In the command below I retrieve the User ID for the user called &lt;code&gt;mattlewis&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws identitystore list-users &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; eu-west-2 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--identity-store-id&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;aws sso-admin list-instances &lt;span class="nt"&gt;--region&lt;/span&gt; eu-west-2 &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'Instances[0].IdentityStoreId'&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; text&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'Users[?UserName==`mattlewis`].UserId'&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; text
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If I wanted to assign a Group rather than a User, then I need to retrieve a Group ID from Identity Centre. The following command retrieves the Group ID for a group called &lt;code&gt;Admin&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws identitystore list-groups &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; eu-west-2 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--identity-store-id&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;aws sso-admin list-instances &lt;span class="nt"&gt;--region&lt;/span&gt; eu-west-2 &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'Instances[0].IdentityStoreId'&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; text&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'Groups[?DisplayName==`Admin`].GroupId'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; text
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is a JSON file called &lt;code&gt;aws-identity-centre-configuration.json&lt;/code&gt; which is made available for convenience. The configuration below assigns the User ID to the Argo CD role of &lt;code&gt;ADMIN&lt;/code&gt;, and the Group ID to the Argo CD role of &lt;code&gt;VIEWER&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"argoCd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"awsIdc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"idcInstanceArn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"REPLACE_WITH_IDC_INSTANCE_ARN"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"idcRegion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"REPLACE_WITH_REGION"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"rbacRoleMappings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ADMIN"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"identities"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"REPLACE_WITH_USER_ID"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SSO_USER"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"VIEWER"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"identities"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"REPLACE_WITH_GROUP_ID"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SSO_GROUP"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can then create the Argo CD capability using the following AWS CLI command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws eks create-capability &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--capability-name&lt;/span&gt; argocd-capability &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cluster-name&lt;/span&gt; eks-test-cluster&lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--type&lt;/span&gt; ARGOCD &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--role-arn&lt;/span&gt; arn:aws:iam::&lt;span class="o"&gt;{&lt;/span&gt;ACCOUNT_ID&lt;span class="o"&gt;}&lt;/span&gt;:role/ArgoCDCapabilityRole &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--delete-propagation-policy&lt;/span&gt; RETAIN &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--configuration&lt;/span&gt; file://aws-identity-centre-configuration.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is equivalent to the following that can be used in a CloudFormation template.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;ArgoCDCapability&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::EKS::Capability&lt;/span&gt;
  &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;CapabilityName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;CapabilityName&lt;/span&gt;
    &lt;span class="na"&gt;ClusterName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;ClusterName&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ARGOCD&lt;/span&gt;
    &lt;span class="na"&gt;RoleArn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;ArgoCDCapabilityRole.Arn&lt;/span&gt;
    &lt;span class="na"&gt;DeletePropagationPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;DeletePropagationPolicy&lt;/span&gt;
    &lt;span class="na"&gt;Configuration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;ArgoCd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;AwsIdc&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;IdcInstanceArn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;IdentityCenterInstanceArn&lt;/span&gt;
          &lt;span class="na"&gt;IdcRegion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;IdentityCenterRegion&lt;/span&gt;
        &lt;span class="na"&gt;RbacRoleMappings&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Identities&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;AdminUserId&lt;/span&gt;
                &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SSO_USER&lt;/span&gt;
            &lt;span class="na"&gt;Role&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ADMIN&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Identities&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;ViewerGroupId&lt;/span&gt;
                &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SSO_GROUP&lt;/span&gt;
            &lt;span class="na"&gt;Role&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;VIEWER&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can deploy the full CloudFormation stack using the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws cloudformation create-stack &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--stack-name&lt;/span&gt; argocd-capability-stack &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--template-body&lt;/span&gt; file://argocd-capability.yaml &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--parameters&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;ParameterKey&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ClusterName,ParameterValue&lt;span class="o"&gt;=&lt;/span&gt;eks-test-cluster &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;ParameterKey&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;IdentityCenterInstanceArn,ParameterValue&lt;span class="o"&gt;=&lt;/span&gt;arn:aws:sso:::instance/ssoins-&lt;span class="o"&gt;{&lt;/span&gt;REPLACE&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;ParameterKey&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;IdentityCenterRegion,ParameterValue&lt;span class="o"&gt;=&lt;/span&gt;eu-west-2 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;ParameterKey&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;AdminUserId,ParameterValue&lt;span class="o"&gt;={&lt;/span&gt;REPLACE_WITH_USER_ID&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;ParameterKey&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ViewerGroupId,ParameterValue&lt;span class="o"&gt;={&lt;/span&gt;REPLACE_WITH_GROUP_ID&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--capabilities&lt;/span&gt; CAPABILITY_NAMED_IAM &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; eu-west-2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some of the properties include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Type&lt;/strong&gt; - This defines the type of EKS Capability to create with valid values of

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ACK&lt;/code&gt; - Amazon Web Services Controllers for Kubernetes (ACK), which lets you manage resources directly from Kubernetes&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ARGOCD&lt;/code&gt; – Argo CD for GitOps-based continuous delivery&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;KRO&lt;/code&gt; – Kube Resource Orchestrator (KRO) for composing and managing custom Kubernetes resources&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;DeletePropagationPolicy&lt;/strong&gt; - This only supported value is &lt;code&gt;RETAIN&lt;/code&gt;, which keeps all resources managed by the capability when the capability is deleted&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Configuration&lt;/strong&gt; - This property defines the configuration settings, with the structure depending on the capability type&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Role&lt;/strong&gt; - The role under the &lt;code&gt;RbacRoleMappings&lt;/code&gt; property defines the Argo CD role to be assigned. The value values are:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ADMIN&lt;/code&gt; - Full administrative access to Argo CD&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;EDITOR&lt;/code&gt; - Edit access to Argo CD resources&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;VIEWER&lt;/code&gt; - Read-only access to Argo CD resources&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Once the capability has been created (which will take a while), the capabilities tab for the cluster in the EKS console will provide the Argo API endpoint and a link to go to the managed hosted Argo UI:&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%2Fkxy71tw7ia5g6bnka9os.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%2Fkxy71tw7ia5g6bnka9os.png" alt="Argo API Endpoint" width="800" height="97"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can click on the link to open up the managed Argo UI. At this point, we will need to click the button to &lt;code&gt;LOG IN VIA SSO&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgb9t6mhj5ykffinvo8fs.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%2Fgb9t6mhj5ykffinvo8fs.png" alt="Argo UI Home Page" width="800" height="351"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In most cases, this will automatically log you directly into the console. At this point, we can see there are no applications currently available.&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%2Fgraw7oyaeqsue0h8g6t1.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%2Fgraw7oyaeqsue0h8g6t1.png" alt="Argo UI Login Successful" width="800" height="368"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can also check the Argo CD Role Based Access Control (RCAC) assignments in the console, and make sure that it matches what we set up in the previous JSON file or in the CloudFormation template.&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%2Fx0oduosuffdadg4u6x3p.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%2Fx0oduosuffdadg4u6x3p.png" alt="Argo RBAC Assignments" width="800" height="130"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Argo CD Capability
&lt;/h2&gt;

&lt;p&gt;Now that the Argo CD capability has been created, we can take a quick look at the same of the changes that have been made to our cluster.&lt;/p&gt;

&lt;p&gt;Firstly, we run a command to look at the EKS Access Entries for the cluster.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws eks list-access-entries &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cluster-name&lt;/span&gt; eks-test-cluster
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;EKS Access Entries are the recommended way to grant users access to the Kubernetes API. Fundamentally, it associates a set of Kubernetes permissions with an IAM identity such as an IAM Role. Running the command above generated the following output.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"accessEntries"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:iam::{ACCOUNT_ID}:role/ArgoCDCapabilityRole"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:iam::{ACCOUNT_ID}:role/aws-reserved/sso.amazonaws.com/eu-west-2/AWSReservedSSO_Developer_b664db2de4791f77"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:iam::{ACCOUNT_ID}:role/aws-service-role/eks.amazonaws.com/AWSServiceRoleForAmazonEKS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:iam::{ACCOUNT_ID}:role/eks-test-cluster-eks-auto-20260116165710199100000002"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can see that four access entries currently exist in the cluster:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The SSO Developer role entry, which is the role I assumed to create the EKS cluster&lt;/li&gt;
&lt;li&gt;An EKS Auto Mode generated role that is used to enable Auto Mode to make authenticated Kubernetes API calls&lt;/li&gt;
&lt;li&gt;An EKS service-linked role that is used to manage the control plane and AWS-side resources of the cluster&lt;/li&gt;
&lt;li&gt;The ArgoCDCapabilityRole role entry, which has been created when enabling the Argo CD EKS Capability which allows the capability to authenticate to the cluster&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can see the permissions that have been automatically granted to each entry in the console.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3c2qhmzmbgrf9382l0xm.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%2F3c2qhmzmbgrf9382l0xm.png" alt="EKS Access Entries" width="800" height="259"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By enabling the EKS Argo CD Capability, the following Custom Resource Definitions (CRDs) have been added to the cluster.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get crds | &lt;span class="nb"&gt;grep &lt;/span&gt;argo  

applications.argoproj.io                        2026-01-16T17:10:56Z
applicationsets.argoproj.io                     2026-01-16T17:10:56Z
appprojects.argoproj.io                         2026-01-16T17:10:57Z
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Deploy a sample application
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Register your EKS cluster with Argo CD
&lt;/h3&gt;

&lt;p&gt;In order to deploy a sample application, the first step is to register the EKS cluster where we want to deploy an application. We do this by by creating a Kubernetes secret, and passing the label of &lt;code&gt;argocd.argoproj.io/secret-type: cluster&lt;/code&gt;. We give the cluster a name, and this is where the mapping between the actual cluster and the ARN happens. With EKS Capabilities you only need to provide the ARN and not the Kubernetes API Server URL as with a self-managed instance. In our case, we are registering a local cluster, as it is the same one as Argo CD is running. The following manifest file is found in the code repository under &lt;code&gt;k8s/argocd&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Secret&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;local-cluster&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argocd&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;argocd.argoproj.io/secret-type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cluster&lt;/span&gt;
&lt;span class="na"&gt;stringData&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;local-cluster&lt;/span&gt;
  &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;arn:aws:eks:eu-west-2:{ACCOUNT_ID}:cluster/eks-test-cluster&lt;/span&gt;
  &lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We then apply this to the cluster&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; local-cluster.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Register an Argo CD Application
&lt;/h3&gt;

&lt;p&gt;Now we can register an Argo CD application. In this case, we will use the guestbook example from the Argo CD project itself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argoproj.io/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Application&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;guestbook&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argocd&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
  &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;repoURL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/argoproj/argocd-example-apps&lt;/span&gt;
    &lt;span class="na"&gt;targetRevision&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HEAD&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;guestbook&lt;/span&gt;
  &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;arn:aws:eks:eu-west-2:{ACCOUNT_ID}:cluster/eks-test-cluster&lt;/span&gt;
    &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a Kubernetes manifest file for a custom resource (an Argo CD Application). Our cluster knows how to handle this as it has installed the CRD when deploying the capability itself. This application tells Argo CD to deploy the guestbook application from the specific public Git repository into the default namespace of the EKS cluster. We can run this file using the following command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; guestbook-application.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We should get back the information that the application has been created &lt;code&gt;application.argoproj.io/guestbook created&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;At this point we can log in to the Argo UI, and see something similar to the following:&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%2Ffog9f78ko4rnimg2nzuh.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%2Ffog9f78ko4rnimg2nzuh.png" alt="Argo CD Unknown Status" width="800" height="407"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If we go into settings, we can see there is a failed connection status with our cluster.&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%2Fj80mzjf06si10lmay2cb.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%2Fj80mzjf06si10lmay2cb.png" alt="Argo CD Cluster Settings" width="800" height="78"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And if we go further and click into the application itself, we see that there are 3 errors in the application conditions.&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%2F64a5v1no43smq6ax2gs6.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%2F64a5v1no43smq6ax2gs6.png" alt="Argo CD Application Conditions Errors" width="800" height="380"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The issue here is that Argo CD cannot build its cache of what exists in the cluster, because it does not have permission to list the cluster-scoped resource &lt;code&gt;PersistentVolume&lt;/code&gt;. It also cannot list resource "ingressclassparams" in API group "eks.amazonaws.com" at the cluster scope to connect to the cluster.&lt;/p&gt;

&lt;h3&gt;
  
  
  Associate an access policy
&lt;/h3&gt;

&lt;p&gt;This issue is caused because the two access policies associated with the Argo CD Capability role (&lt;code&gt;AmazonEKSArgoCDPolicy&lt;/code&gt; and &lt;code&gt;AmazonEKSArgoCDClusterPolicy&lt;/code&gt;) do not give the required permissions to access or mutate the cluster resources.&lt;/p&gt;

&lt;p&gt;The best practice in this case is to determine the minimum permissions required, and add these to an access policy. However, the fastest solution is to add the &lt;code&gt;AmazonEKSClusterAdminPolicy&lt;/code&gt; with cluster scope to the access entry, which we can do using the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws eks associate-access-policy &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cluster-name&lt;/span&gt; eks-test-cluster &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--principal-arn&lt;/span&gt; arn:aws:iam::424727766526:role/ArgoCDCapabilityRole &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--policy-arn&lt;/span&gt; arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--access-scope&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cluster
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once we have run this command, Argo CD will now be able to connect successfully to the cluster:&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%2Fqqg96wb5vwnmjkmg8cq1.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%2Fqqg96wb5vwnmjkmg8cq1.png" alt="Argo CD Cluster Successful" width="800" height="86"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And the application can be synced and is healthy.&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%2Fuw9cjoarqpvvb4ke548d.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%2Fuw9cjoarqpvvb4ke548d.png" alt="Argo CD Application Healthy" width="800" height="412"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>tutorial</category>
      <category>kubernetes</category>
      <category>beginners</category>
    </item>
    <item>
      <title>AWS Certified Machine Learning Engineer Core Concepts</title>
      <dc:creator>Matt Lewis</dc:creator>
      <pubDate>Sat, 04 Oct 2025 13:32:38 +0000</pubDate>
      <link>https://dev.to/aws-heroes/aws-certified-machine-learning-engineer-core-concepts-5ekg</link>
      <guid>https://dev.to/aws-heroes/aws-certified-machine-learning-engineer-core-concepts-5ekg</guid>
      <description>&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;Earlier this year I passed the AWS Machine Learning Engineer - Associate exam. I spent time making sure I understood the core concepts before taking the exam, and made a lot of notes. The intent of this post is to summarise the concepts essential to pass the exam. Based on my experience, knowing these concepts will get you at least half way there. Layer on knowledge of the AWS AI Services, and focus on SageMaker and all its capabilities, and the certification will be yours.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Data Preprocessing&lt;/li&gt;
&lt;li&gt;SageMaker Built-In Algorithms&lt;/li&gt;
&lt;li&gt;Model Development&lt;/li&gt;
&lt;li&gt;Evaluating Model Accuracy&lt;/li&gt;
&lt;li&gt;Improving Model Accuracy&lt;/li&gt;
&lt;li&gt;Additional Topics to Study&lt;/li&gt;
&lt;li&gt;Other Study Guides&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Data Preprocessing &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Data preprocessing ensures that data is in the right shape and of the right quality to be used for training.&lt;/p&gt;

&lt;p&gt;Labelling data is important for models to learn effectively, and this is where services such as Mechanical Turk and SageMaker Ground Truth come in. Mechanical Turk is an online marketplace to access an on-demand global workforce. SageMaker Ground Truth provides built-in workflows to automate data labelling, and can use your own workforce, third-party vendors from the AWS marketplace, or Mechanical Turk.&lt;/p&gt;

&lt;p&gt;Cleaning data can include removing outliers and duplicates, replacing inaccurate or irrelevant data, and correcting missing data.&lt;/p&gt;

&lt;p&gt;Approaches for inputting missing data include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Mean replacement&lt;/strong&gt; – replacing the missing values with the mean value from the rest of the column. The mean value is the average, which means it can be distorted by outliers. Therefore, the median value (which is the middle value when data is sorted) may be a better choice&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;KNN (K-Nearest Neighbour)&lt;/strong&gt; – Find the ‘K’ nearest’ most similar rows and average their values. This assumes numeric data.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Balancing data (for datasets with underrepresented categories) can be achieved using one of the following methods:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Random oversampling&lt;/strong&gt; – this method randomly duplicates samples from the minority category. For example, if you were building a fraud detection model and you had 1000 examples of normal transactions and only 50 of fraudulent transactions, you would duplicate the fraudulent transactions until you had an equal proportion&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Random Undersampling&lt;/strong&gt; – this method randomly removes samples from the overrepresented category to achieve the equal proportion. This would typically be used when you have a large dataset, or you would want to reduce the size of your dataset to make training the model quicker&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Synthetic Minority Oversampling Technique (SMOTE)&lt;/strong&gt; – this approach generates new synthetic samples of the minority category by interpolating between existing samples using nearest neighbours.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Encoding&lt;/strong&gt; is the concept around converting data (typically categorical data where the data represents a category or group) into a numerical format that can be well understood by a model. The main types of encoding are the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Label Encoding&lt;/strong&gt; – assigns each category a unique number e.g. Red=0, Green=1, Blue=2. There is no order implied by this encoding&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;One-Hot Encoding&lt;/strong&gt; – creates binary columns for each category. This means if there is a category called colour there would be additional columns created for each unique value such as a column for Red and one for Blue and for Green. The value for each column would be assigned 1 if it is true, else 0.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Ordinal Encoding&lt;/strong&gt; – this is similar to label encoding but is used when there is a ranked ordering between values in a category. For a category called ‘size’ you could map Small to 0, Medium to 1 and Large to 2.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Outliers are data points in a data set that deviate significantly from the general patterns. One way of detecting outliers in training data is to measure how many standard deviations a data point is from the mean of a dataset. This is often called a &lt;strong&gt;z-score&lt;/strong&gt; or standard score. Data points that lie more than one standard deviation from the mean can be considered unusual.&lt;/p&gt;

&lt;p&gt;Outliers can be handled in different ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Delete the record&lt;/strong&gt; - if the outlier is clearly an error and there is enough training data, you can just delete that record.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Feature scaling or normalisation&lt;/strong&gt; – this aims to transform the numeric values so that are all values are on the same scale, often between 0 and 1. This rescaling makes the values more comparable&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Standardisation&lt;/strong&gt; – is similar to normalisation but instead of scaling values from 0 to 1, it rescales the features to have a mean of 0 and standard deviation of 1&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Binning&lt;/strong&gt; – takes a continuous numerical feature and splits into a set of intervals or bins. Each value is then assigned to a bin which can cover up imprecision or uncertainty e.g. someone aged 110 could end up in a bin which is “70+”.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After data has been cleaned up and encoded, you can fine tune or create new features in your dataset through feature engineering. There are other methods such as &lt;strong&gt;bag-of-words&lt;/strong&gt; and &lt;strong&gt;N-gram&lt;/strong&gt; that can be used to extract key information from text data.&lt;/p&gt;

&lt;p&gt;In machine learning, &lt;strong&gt;dimensionality&lt;/strong&gt; refers to the number of features in your dataset. If you have a dataset with 3 features (age, income and height), it exists in a 3-dimensional space. The &lt;strong&gt;curse of dimensionality&lt;/strong&gt; refers to the problems that arise when you have too many features. When this happens, the data becomes sparse (e.g. spread out too thinly in the feature space), and it is hard to find meaningful patterns.&lt;/p&gt;

&lt;p&gt;There are a number of unsupervised reduction techniques that can help to distil many features into a smaller more manageable number:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Principal component analysis (PCA)&lt;/strong&gt; – this technique retains most of the variation in the original features but reduces the overall number of features. It works by transforming features into a new set of features called principal components&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;K-Means&lt;/strong&gt; – this technique uses a clustering algorithm to group similar data points into K clusters. It does not create new features but assigns a cluster label to each data point.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  SageMaker Built-In Algorithms &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Before you can train your model, you need to select a machine learning algorithm to use. Amazon SageMaker provides a suite of built-in algorithms, pre-trained models, and pre-built solution templates to help data scientists and machine learning practitioners get started on training and deploying machine learning models quickly. It is worth understanding each of these algorithms at a high-level, understanding which ones are used for supervised versus unsupervised learning, and their main uses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Linear Learner&lt;/strong&gt; - A supervised learning algorithm suited for general classification (Logistic Regression) and regression (Linear Regression) tasks.  It makes predictions based on labelled data. In simpler terms, the model is given examples where each example has some features (like size, price, or age) and an outcome (like a house price or a category). For classification tasks, the model sorts data into categories, such as whether a house is expensive or not. For regression tasks, it predicts a specific value, like the actual price of a house. It assumes linear regression and must pre-process missing values.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;K-Means&lt;/strong&gt; - An unsupervised algorithm designed for clustering or grouping data points based on their features (chosen attribute), without needing labelled data&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;BlazingText&lt;/strong&gt; - A supervised algorithm used for Natural Language Processing tasks like text classification.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Seq2Seq&lt;/strong&gt; - A supervised algorithm specifically designed for sequence-to-sequence tasks, such as predicting the next word in a sequence, making it ideal for tasks like language translation or text generation&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DeepAR&lt;/strong&gt; - A supervised algorithm used to forecast time-series predictions by using recurrent neural networks (RNN)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;XGBoost&lt;/strong&gt; - A supervised learning algorithm used for both classification and regression tasks — especially when you care about speed and performance. It is an optimized, scalable implementation of gradient boosting that builds an ensemble of decision trees in a sequential manner. Often outperforms other models in competitions (e.g., Kaggle)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Random Cut Forest&lt;/strong&gt; - An unsupervised algorithm used to identify abnormal data points within a data set e.g. anomaly detection&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Semantic Segmentation&lt;/strong&gt; - A supervised algorithm that provides pixel-level classification but does not label objects with bounding boxes. It is typically used to classify individual pixels by tagging each pixel with a specified class&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Principal Component Analysis (PCA)&lt;/strong&gt; - An unsupervised algorithm used for dimensionality reduction&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Image Classification&lt;/strong&gt; - A supervised algorithm that is used to label entire images, not individual objects. It simply assigns a single label to an entire image, categorising it based on the predominant features. It cannot identify or count multiple objects within a single image.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Object Detection&lt;/strong&gt; - A supervised algorithm used to identify and classify multiple objects within an image, assigning bounding boxes and confidence scores. It draws bounding boxes around detected objects and classifies them into different categories, making it very useful for tasks where you need to recognise what is in the image and determine the exact location of each object. This algorithm is well-suited for scenarios that require counting specific items, such as animals, in drone imagery, as it can distinguish between individual objects even in complex scenes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Object2Vec&lt;/strong&gt; - A supervised algorithm primarily used to learn vector embeddings of discrete objects. Its typically used in recommendation systems, document classification or semantic similarity tasks, not computer vision or image processing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IP Insights&lt;/strong&gt; - An unsupervised algorithm used to detect anomalies in IP address usage patterns. It captures associations between these IP addresses and various entities, such as user IDs or account numbers. For instance, you can use it to detect a user attempting to log into a web service from an anomalous IP address. Additionally, it helps identify accounts that create computing resources from unexpected IP addresses. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Latent Dirichlet Allocation (LDA)&lt;/strong&gt; - An unsupervised learning technique designed to represent a collection of documents as a combination of various topics. LDA is primarily used to identify a specified number of topics within a set of text documents. The LDA algorithm is a powerful tool for text mining and natural language processing tasks. It allows companies to sift through vast amounts of textual data and discern patterns that might take time to be apparent. Since LDA is an unsupervised method, the topics are not specified up front, and the discovered topics may not necessarily match human categorisations. Instead, LDA learns the topics as a probability distribution over the words in the documents, and each document is characterised as a mixture of these topics.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Neural Topic Model (NTM)&lt;/strong&gt; - An unsupervised algorithm used for organising documents into topics. It is just like LDA — but it's based on neural networks rather than probabilistic graphical models.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Factorization Machines&lt;/strong&gt; - A supervised algorithm designed to handle sparse data, making it ideal for recommendation systems where user-item interactions are often sparse. It is primarily used for recommendation systems and ranking predictions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Text Classification – TensorFlow algorithm&lt;/strong&gt; - A supervised algorithm designed to classify text into predefined categories. &lt;/p&gt;

&lt;h2&gt;
  
  
  Model Development &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Hyperparameters&lt;/strong&gt; are external configuration variables used to control a training model, improve model performance and the model outcome. Hyperparameters are set before training. This can be done manually, although SageMaker offers an intelligent version of hyperparameter tuning methods based on Bayesian search theory designed to find the best model in the shortest time. Amazon SageMaker AI automatic model tuning (AMT) finds the best version of a model by running many training jobs on your dataset. Amazon SageMaker AI automatic model tuning (AMT) is also known as hyperparameter tuning and supports Hyperband, a new search strategy.&lt;/p&gt;

&lt;p&gt;Common hyperparameters include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Epoch&lt;/strong&gt; – the number of times the entire training dataset is shown to the network during training. A smaller epoch value means faster training, but the model might not learn enough patterns and end up underfitting. A larger epoch value gives more opportunity to refine weights and give better convergence, but will take longer to train and may end up memorising training data and so overfitting&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Learning rate&lt;/strong&gt; – the rate at which an algorithm updates estimates. Too high a learning rate means you might overshoot the optimal solution. Too small a learning rate will take too long to find the optimal solution&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Batch size&lt;/strong&gt; – how many batch training samples are used within each batch of each epoch. Large batch sizes are faster per epoch as it will fill the GPU, but risk worse generalisation and can end up getting stuck in the wrong solution. Small batch sizes are slower per epoch but can provide better generalisation.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note that hyperparameters are not related to &lt;strong&gt;inference parameters&lt;/strong&gt;. Inference parameters are settings you can adjust during inference, that influence the response from the model. The most common are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Temperature&lt;/strong&gt;: Temperature is a value between 0 and 1, and it regulates the creativity of the model's responses. Use a lower temperature if you want more deterministic responses, and use a higher temperature if you want creative or different responses for the same prompt&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Top K&lt;/strong&gt;: The number of most-likely candidates that the model considers for the next token. Choose a lower value to decrease the size of the pool and limit the options to more likely outputs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Top P&lt;/strong&gt;: The percentage of most-likely candidates that the model considers for the next token. Choose a lower value to decrease the size of the pool and limit the options to more likely outputs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Evaluating Model Accuracy &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Metrics are used to measure the performance and accuracy of a machine learning model. These metrics can typically be broken down into classification metrics and regression metrics.&lt;/p&gt;

&lt;p&gt;With classification, the goal of the model is to predict a label or class (category) for the given input. With binary classification, there are only two possible outputs (positive or negative). This is used to predict whether an image is a dog or not, or whether an email is spam or not. With multi-class classification, there are more than two possible outputs, such as predicting whether an animal is a dog, cat or cow.&lt;/p&gt;

&lt;p&gt;With regression, the goal of the model is to predict a numerical value. This could be predicting a house or stock price, or a person's annual income given certain inputs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Classification Metrics
&lt;/h3&gt;

&lt;p&gt;The confusion matrix is a great way to help understand common classification metrics. &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%2Fr6m0fsc8qbn74xccoiii.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%2Fr6m0fsc8qbn74xccoiii.png" alt="Confusion Matrix" width="678" height="177"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recall&lt;/strong&gt; - Recall is the percentage of positives correctly predicted. It is focused on how many of the actual positives did the model get right. You use it when you prefer to catch as many positives as possible, even if some are incorrect. It is a good metric when false negatives are costly e.g. fraud detection or cancer screening where it is better to flag more cases (even if some are wrong) than to miss a true positive&lt;/p&gt;

&lt;p&gt;It is calculated as: &lt;code&gt;Recall = TP / (TP + FN)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Precision&lt;/strong&gt; - Precision is all around correct positives. All positive predictions include both true positives and false positives (those predicted as positive but are actually negative). This means it is a good metric when false positives are costly e.g. spam email when you don't want to mark legitimate emails as spam, or object detection in autonomous vehicles, where a false positive can induce sudden unnecessary braking.&lt;/p&gt;

&lt;p&gt;It is calculated as: &lt;code&gt;Precision = TP / (TP + FP)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;A model with &lt;strong&gt;high precision and low recall&lt;/strong&gt; catches few positives but is rarely wrong.&lt;/p&gt;

&lt;p&gt;A model with &lt;strong&gt;high recall and low precision&lt;/strong&gt; catches most positives but includes many false alarms.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;F1 Score&lt;/strong&gt; - The F1 Score is the harmonic mean of precision and recall. It is used when you need a balance betweeb both.&lt;/p&gt;

&lt;p&gt;It is calculated as: &lt;code&gt;F1 Score = 2 x ((Precision x Recall) / (Precision + Recall))&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Accuracy&lt;/strong&gt; - Accuracy measures overall correctness — how often the model was right, regardless of class. It considers all predictions (true positives, true negatives, false positives, and false negatives).&lt;/p&gt;

&lt;p&gt;It is calculated as: &lt;code&gt;Accuracy = TP + TN / TP + TN + FP + FN&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AUC and ROC&lt;/strong&gt; - The ROC curve is a graphical plot that helps visualise how well a binary classification model performs across different threshold values. It is a plot of true positive rate (recall) versus false positive rate and helps you see this trade off between True Positives and False Positives. &lt;/p&gt;

&lt;p&gt;The Area under the Curve (AUC) is a single scalar value between 0 and 1 of how well the classification model can separate the positive and negative predictions. A value of 0.5 means the model performs no better than a random classifier. A value of 1.0 is a perfect classifier. &lt;/p&gt;

&lt;h3&gt;
  
  
  Regression Metrics
&lt;/h3&gt;

&lt;p&gt;If you are using regression where you are predicting a number and not just a classification, then there are other metrics:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mean Absolute Error (MEA)&lt;/strong&gt; - MEA measures the average absolute difference between the predicted values and the actual values. It tells you on average, how much your models predictions are off from the true values. A lower score means a better model. It is simple to understand and is not affected by outliers. MAE is robust to outliers because it handles them linearly, not exponentially. This makes it a good choice when you don’t want a few bad predictions to dominate the error metric.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mean Squared Error (MSE)&lt;/strong&gt; - MSE averages the squared difference between actual and predicted values. Because it squares the values, outliers become amplified. This makes MSE more sensitive to outliers. You would choose MSE (Mean Squared Error) over MAE (Mean Absolute Error) when you want to penalize large errors more heavily and are more concerned with model performance on extreme values.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RMSE (Root Mean Square Error)&lt;/strong&gt; - RMSE is a metric used to measure the differences between predicted values and actual values in a regression problem. It calculates the square root of the average squared differences between the predicted and actual values. A lower Root Mean Square Error value indicates better model performance. Since the errors are squared before averaging, larger errors have a bigger impact (this makes RMSE sensitive to outliers).&lt;/p&gt;

&lt;p&gt;You would use RMSE (Root Mean Squared Error) over MSE (Mean Squared Error) when you want the error metric to be in the same units as the target variable, making it more interpretable. For example, if you're predicting house prices in dollars, RMSE is in dollars, while MSE is in squared dollars, which is less intuitive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;R Squared&lt;/strong&gt; - R-Squared measures the square of the correlation coefficient between observed outcomes and predicted. It measures how well your regression model explains the variability of the target (dependent) variable. A score of 1 means the model explains all the variance perfectly. A score of 0 means the model explains none of the variance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Improving Model Accuracy &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Understanding model fit is important when understanding the root cause for poor model accuracy. &lt;/p&gt;

&lt;p&gt;Two common terms that come up to describe model performance are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Overfitting&lt;/strong&gt; – a model that is overfitting has learned patterns in the training data that don’t generalise out to the real world. This  means that it has high accuracy on the training data set, but lower accuracy on evaluation data sets&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Underfitting&lt;/strong&gt; – a model that is underfitting performs poorly on the training data and in the real world. This is because the model is unable to capture the relationship between the input examples and the target values. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Regularization techniques&lt;/strong&gt; are intended to prevent overfitting. Common techniques include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Dropout&lt;/strong&gt; – this is a technique where random neurons are temporarily dropped out (e.g. ignore) during each training iteration. This means the network can’t rely too heavily on any specific neuron or connection&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Early Stopping&lt;/strong&gt; – this is a technique where you stop training the neural network before it overfits the training data. It works by monitoring validation loss and accuracy and stopping training when the model stops improving.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;L1 and L2 Regularization&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;L1 and L2 Regularization are techniques used to prevent overfitting by penalising large model weights. In a machine learning model, a weight is a numeric parameter that connects an input feature to an output. A large weight value means the model is putting an extremely strong emphasis on that specific feature, which means the model becomes very sensitive to small changes in inputs, which can lead to overfitting. These techniques add a penalty term to the loss function to discourage large weights:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;L1 Regularization (Lasso)&lt;/strong&gt; – the penalty is the sum of the absolute value of the weights. It shrinks some weights entirely to zero to create sparse models. This is a form of feature selection (removing irrelevant features). You should use this when you suspect only a few features are important. It is computationally inefficient.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;L2 Regularization (Ridge)&lt;/strong&gt; – the penalty is the sum of the square of the weights. This shrinks the weights but does not make them zero. It helps keep the model simpler and reduces sensitivity. It is computationally efficient.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Additional Topics to Study &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;The two other main topic areas you need to understand are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AWS AI Services&lt;/strong&gt; - these are the managed AWS services that offer a simpler entry point than building your own model. You will need to a good understanding of each service and what it is used for, so you can distinguish between Amazon Lex and Amazon Polly, and between Amazon Translate and Amazon Transcribe.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Amazon SageMaker&lt;/strong&gt; - Amazon SageMaker is a service that provides a whole host of features and capabilities you need to be aware of. You need to understand which feature you can use to detect bias; which to import, prepare and transform data; which to share curated features, and so on.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Other Study Guides &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AWS Certification Page&lt;/strong&gt; - the &lt;a href="https://aws.amazon.com/certification/certified-machine-learning-engineer-associate/" rel="noopener noreferrer"&gt;AWS certification home page&lt;/a&gt; for this exam includes the study guide and links to additional resources&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AWS SkillBuilder&lt;/strong&gt; - the &lt;a href="https://skillbuilder.aws/learning-plan/A2FGY8CH1P/exam-prep-plan-aws-certified-machine-learning-engineer--associate-mlac01--english/3YFU86SSKN" rel="noopener noreferrer"&gt;AWS official learning plan&lt;/a&gt; which is available for free alongside an official set of practice exam questions. Additional material including longer review sections, extra questions and labs are available with a subscription.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Udemy&lt;/strong&gt; - the &lt;a href="https://www.udemy.com/course/aws-certified-machine-learning-engineer-associate-mla-c01/" rel="noopener noreferrer"&gt;certification course&lt;/a&gt; provided by Stephane Maarek and Frank Kane comes highly recommended&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Pluralsight&lt;/strong&gt; - this &lt;a href="https://www.pluralsight.com/paths/aws-certified-machine-learning-engineer-associate-mlac01" rel="noopener noreferrer"&gt;certification course&lt;/a&gt; by Pluralsight also includes labs. Pluralsight offer a 10 day free individual trial and monthly subscriptions which may work for some&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tutorials Dojo&lt;/strong&gt; - this set of &lt;a href="https://portal.tutorialsdojo.com/courses/aws-certified-machine-learning-engineer-associate-practice-exams-mla-c01-2025/" rel="noopener noreferrer"&gt;practice questions&lt;/a&gt; is a great way to get used to the style of exam in various modes&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>ai</category>
      <category>certification</category>
      <category>learning</category>
    </item>
    <item>
      <title>Next Gen Developer Experience with Amazon Q Developer</title>
      <dc:creator>Matt Lewis</dc:creator>
      <pubDate>Thu, 08 May 2025 08:55:15 +0000</pubDate>
      <link>https://dev.to/aws-heroes/next-gen-developer-experience-with-amazon-q-developer-1eja</link>
      <guid>https://dev.to/aws-heroes/next-gen-developer-experience-with-amazon-q-developer-1eja</guid>
      <description>&lt;p&gt;There have been massive advances in the capabilities and features supported by Amazon Q Developer over the last few months. A number of these really stood out for me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;At the beginning of March the new enhanced Amazon Q Developer CLI agent was released with the power of Claude 3.7 Sonnet step-by-step reasoning. This also  gave the agent access to tools such as the AWS CLI. Read more in the &lt;a href="https://aws.amazon.com/blogs/devops/introducing-the-enhanced-command-line-interface-in-amazon-q-developer/" rel="noopener noreferrer"&gt;blog post&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;At the end of April the Amazon Q Developer CLI was further enhanced with support for Model Context Protocol (MCP) to provide even more context. Read more in the &lt;a href="https://aws.amazon.com/blogs/devops/extend-the-amazon-q-developer-cli-with-mcp/" rel="noopener noreferrer"&gt;blog post&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;At the beginning of May, Amazon Q Developer support was integrated into GitHub in preview. Read more in the &lt;a href="https://aws.amazon.com/blogs/aws/amazon-q-developer-in-github-now-in-preview-with-code-generation-review-and-legacy-transformation-capabilities/" rel="noopener noreferrer"&gt;blog post&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With all of these improvements, I wanted to see if there was a way of bringing them together to meet a coherent use case. This use case was  to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Take a task assigned to me from a Jira Kanban board&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Implement the requested functionality&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Push the code up to GitHub as the source code repository&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run a check for security vulnerabilities and code quality issues&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Raise a Pull Request&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Move the task along on the Kanban board&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The goal was to show how these tools can make life easier for a software engineer, and greatly increase their productivity. Let's see how I got on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Set up Jira
&lt;/h2&gt;

&lt;p&gt;I am using a hosted version of Jira Cloud using the free tier provided by Atlassian. The first thing I did was to create a new Jira project that sets up a Kanban board using a software development supporting template. &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%2F09ab4hvux2gm4c5johqa.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%2F09ab4hvux2gm4c5johqa.png" alt="Create Jira Project" width="800" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next I created a new Jira task. The task was to "create a classic snake game written in python using pygame", and I assigned it to myself. Although this is a contrived example, you could easily equate this to a new feature on an existing service.&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%2F94p5455ppmm8b5qoocpn.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%2F94p5455ppmm8b5qoocpn.png" alt="Create Jira Task" width="794" height="767"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once created, we can see this task in to "To Do" section of the Kanban board.&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%2Fktm5k27l9nj9q0cioyti.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%2Fktm5k27l9nj9q0cioyti.png" alt="Jira To Do Kanban" width="800" height="233"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I also created a new GitHub repository which is cloned to my workspace.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Configure MCP Servers
&lt;/h2&gt;

&lt;p&gt;The next step was to setup the Amazon Q CLI to access both Atlassian and GitHub. Amazon Q CLI acts as an MCP Client, and it can access MCP Servers that have been configured in a &lt;code&gt;mcp.json&lt;/code&gt; file. This files needs to be located in &lt;code&gt;~/.aws/amazonq&lt;/code&gt;. You can find out more details in this &lt;a href="https://dev.to/aws/configuring-model-context-protocol-mcp-with-amazon-q-cli-e80"&gt;blog post&lt;/a&gt; by Ricardo Sueiras.&lt;/p&gt;

&lt;p&gt;I want to run these MCP Servers in a container, and without access to Docker Desktop I configure them to use Podman. My configuration is shown below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "mcpServers": {
    "github-mcp-server": {
      "command": "podman",
      "args": [
        "run",
        "--rm",
        "--interactive",
        "--env",
        "GITHUB_PERSONAL_ACCESS_TOKEN",
        "ghcr.io/github/github-mcp-server"
      ],
      "env": {"GITHUB_PERSONAL_ACCESS_TOKEN": "github_pat_xxx"}
    },
    "mcp-atlassian": {
      "command": "podman",
      "args": [
        "run",
        "-i",
        "--rm",
        "-e", "JIRA_URL",
        "-e", "JIRA_USERNAME",
        "-e", "JIRA_API_TOKEN",
        "ghcr.io/sooperset/mcp-atlassian:latest"
      ],
      "env": {
        "JIRA_URL": "https://xxx.atlassian.net/",
        "JIRA_USERNAME": "xxx@email.com",
        "JIRA_API_TOKEN": "XXXXX"
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This required creating a Personal Access Token in GitHub, and an API Token in Atlassian.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Run Amazon Q Developer CLI
&lt;/h2&gt;

&lt;p&gt;After making the changes to the &lt;code&gt;mcp.json&lt;/code&gt; configuration, I launched Amazon Q CLI from the terminal window in my Visual Studio Code IDE. You can see that the two MCP Servers have been loaded and are accessible.&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%2F9xup59yca1z89snvw4q1.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%2F9xup59yca1z89snvw4q1.png" alt="Launch Amazon Q CLI" width="800" height="216"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I start by asking the Amazon Q CLI to “get the latest task from Jira that is assigned to me”. The Amazon Q CLI returns wanting to know more details, before it can use the configuration to retrieve information about the 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%2F16lrvkkndqds6ctjdoyk.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%2F16lrvkkndqds6ctjdoyk.png" alt="Get Latest Jira Task" width="800" height="147"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I tell the Amazon Q CLI that I am using Jira Cloud and to search across all projects. I am then told that the Amazon Q CLI wants to run a tool provided by the &lt;code&gt;mcp_atlassian&lt;/code&gt; MCP Server. I am prompted to either press &lt;code&gt;t&lt;/code&gt; to always trust the tool for the session, &lt;code&gt;y&lt;/code&gt; to allow the tool to be executed this time without trusting for the session, or &lt;code&gt;n&lt;/code&gt; to not let the tool be executed. I will be answering &lt;code&gt;y&lt;/code&gt; to all of these prompts.&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%2Frkxq54dqvdxgkxkbdomx.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%2Frkxq54dqvdxgkxkbdomx.png" alt="Jira Search MCP tool" width="800" height="220"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After running the tool, the Amazon Q CLI has found the task and displays all of the details.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa8v0vaxwvgtp2j1w81me.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%2Fa8v0vaxwvgtp2j1w81me.png" alt="Jira Task details returned" width="800" height="178"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I ask Amazon Q CLI to help me implement the functionality, and it goes away and generates all the code required. At this point, the code is in memory, and I am asked if I want to save the code to a file in my current directory.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fukytd6ws1vatnbo5mxg9.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%2Fukytd6ws1vatnbo5mxg9.png" alt="Q CLI Implement Functionality" width="800" height="338"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I tell the Amazon Q CLI to create a new branch and add the file to this new branch. After running the relevant git commands to create a new branch, the Amazon Q CLI switches to this branch, and then writes the code to a new file in this branch.&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%2Fvjgpz6v0xq2xpsok0vd8.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%2Fvjgpz6v0xq2xpsok0vd8.png" alt="Q CLI Create New Branch" width="800" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After successfully writing the code to a new branch, the Amazon Q CLI commits the changes with a message referencing the specific Jira task number that it still has in its current session 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%2Frvavklm8kbk51di5c6uo.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%2Frvavklm8kbk51di5c6uo.png" alt="Q CLI Commit Changes" width="800" height="379"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At this point, I could prompt to make more enhancements to the game. The Amazon Q CLI even gives suggestions of areas of the game it could improve. Instead, I just ask it to update the README file with instructions on how to play the game, and then make another commit.&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%2Fqzwpo83609tygdl1z0ca.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%2Fqzwpo83609tygdl1z0ca.png" alt="Update README.md" width="800" height="407"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Amazon Q CLI now asks if I want to make any other changes. I'm happy with those that have been made so far, so ask it to make a pull request for these. Notice the first request to create a pull request fails. Amazon Q CLI apologises, and tries another approach, this time pushing the branch to GitHub and then creating the pull request which succeeds.&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%2F876qgapywrxwepeeidx0.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%2F876qgapywrxwepeeidx0.png" alt="Create Pull Request" width="800" height="373"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After creating the pull request, the Amazon Q CLI knows from its session context and its reasoning, that we should also update the original task in Jira. It interacts with tools in the Atlassian MCP Server to transition the task to the "In Progress" state and add a relevant comment.&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%2Fi9ktluhl1ul1ogywhgay.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%2Fi9ktluhl1ul1ogywhgay.png" alt="Update Jira" width="800" height="367"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Jira Kanban Board
&lt;/h2&gt;

&lt;p&gt;At this point in time, I go across to the Kanban board in Jira and can see that the task has been transitioned to "In Progress".&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%2Fg7tbe4kkamg1800zqzb0.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%2Fg7tbe4kkamg1800zqzb0.png" alt="Jira Task In Progress" width="800" height="237"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Clicking into the task, I can see the comment that has been added to the task. This gives details about the functionality implemented to meet the task description, alongside a working link to the open PR in GitHub.&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%2Fnge3ot2sbsqd0ec7hfhl.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%2Fnge3ot2sbsqd0ec7hfhl.png" alt="Jira Task Comment" width="800" height="482"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Code Scanning in GitHub
&lt;/h2&gt;

&lt;p&gt;The final task I wanted to carry out was to run a check for security vulnerabilities and code quality issues. I have already configured my GitHub account with the Amazon Q Developer application. This means that as soon as the pull request was raised, the &lt;code&gt;amazon-q-developer&lt;/code&gt; application automatically scanned the changes in the PR. Happily, there were no security or code quality issues found. If there were, the new application would have automatically generated code suggestions to fix the findings.&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%2Fbc3bkc0caqb9k5kdxrq0.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%2Fbc3bkc0caqb9k5kdxrq0.png" alt="GitHub Integration" width="800" height="812"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;I can't remember the last time I tested out new features and services and genuinely felt so convinced we are now starting to see a change in how we will engineer applications in the software industry. The value of software engineering still remains, even more so when working on complex problems for which generative AI solutions do not have the corpus of knowledge to be trained on. However, this showed to me how these new capabilities can help in reducing the context switching, and the need to move between various tools, copying data between them. The next generation of developer experience is well and  truly upon it, so I'd urge everyone to try it out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Biography
&lt;/h2&gt;

&lt;p&gt;As Chief AWS Architect at IBM in the UK, I am responsible for growing the AWS capability and community within one of the fastest growing AWS consulting partners globally. This often gives me the opportunity to try out the latest features in preview before they go into general availability. You'll often find me blogging about my experience, but please reach out if there are services you'd like to know more about.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>tutorial</category>
      <category>aws</category>
    </item>
    <item>
      <title>Java code transformation using the Amazon Q Developer GitHub Integration</title>
      <dc:creator>Matt Lewis</dc:creator>
      <pubDate>Tue, 06 May 2025 10:30:37 +0000</pubDate>
      <link>https://dev.to/aws-heroes/java-code-transformation-using-the-amazon-q-developer-github-integration-6fd</link>
      <guid>https://dev.to/aws-heroes/java-code-transformation-using-the-amazon-q-developer-github-integration-6fd</guid>
      <description>&lt;p&gt;AWS have launched the Amazon Q Developer integration with GitHub. I was keen to try this out, and in this post, I walk through how to get started and use the integration to upgrade a Java project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Setup
&lt;/h2&gt;

&lt;p&gt;The first step is to install the Amazon Q Developer application from the GitHub Marketplace found at this URL - &lt;a href="https://github.com/apps/amazon-q-developer" rel="noopener noreferrer"&gt;https://github.com/apps/amazon-q-developer&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%2Fk1m99jkbo3hmdksnuedj.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%2Fk1m99jkbo3hmdksnuedj.png" alt="Amazon Q Developer application" width="800" height="433"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then click on &lt;code&gt;Install&lt;/code&gt; and select which repositories you want to allow the application to access.&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%2Fqgiw22a6fgkrrc2swolp.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%2Fqgiw22a6fgkrrc2swolp.png" alt="Install Q Developer application" width="800" height="1006"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also optionally register the application installation with your AWS account to increase your usage limits. This is a two-step process. A landing page in your AWS console allows you to authorise Amazon Q Developer to access your GitHub account.&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%2Fxkkw3vcmjnzvq72t1je5.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%2Fxkkw3vcmjnzvq72t1je5.png" alt="Register App Installation" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This redirects you to GitHub to complete the authorisation process.&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%2F74np9gaie9lo1646f4wl.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%2F74np9gaie9lo1646f4wl.png" alt="Authorise GitHub" width="800" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You are then returned the AWS Console to provide a registration name and complete the registration process.&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%2F73qw3tt8hyewzg91lx9i.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%2F73qw3tt8hyewzg91lx9i.png" alt="Complete Registration Process" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup GitHub Actions
&lt;/h2&gt;

&lt;p&gt;Before you can get started on running a transformation request, you need to enable GitHub Actions for the repository and ensure a runner is available online.&lt;/p&gt;

&lt;p&gt;This involves creating a &lt;code&gt;main.yml&lt;/code&gt; file within a &lt;code&gt;.github/workflows/&lt;/code&gt; folder structure. The workflow I used is shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Q Code Transformation

on:
  push:
    branches:
      - 'Q-TRANSFORM-issue-*'

env:
   MAVEN_CLI_OPTS: &amp;gt;-
     -Djava.version=${{ contains(github.event.head_commit.message, 'Code transformation completed') &amp;amp;&amp;amp;  '17' || '11' }}

jobs:
  q-code-transformation:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-java@v3
        with:
          java-version: ${{ contains(github.event.head_commit.message, 'Code transformation completed') &amp;amp;&amp;amp; '17' || '11' }}
          distribution: 'adopt'

      - name: Build and copy dependencies
        run: |
          mvn ${{ env.MAVEN_CLI_OPTS }} clean install -U
          mvn ${{ env.MAVEN_CLI_OPTS }} verify
          mvn ${{ env.MAVEN_CLI_OPTS }} dependency:copy-dependencies -DoutputDirectory=dependencies -Dmdep.useRepositoryLayout=true -Dmdep.copyPom=true -Dmdep.addParentPoms=true

      - name: Upload artifacts
        uses: actions/upload-artifact@v4
        with:
          name: q-code-transformation-dependencies
          path: dependencies
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a workflow named &lt;code&gt;Q Code Transformation&lt;/code&gt; that is triggered when code is pushed to a branch that matches the pattern &lt;code&gt;Q-TRANSFORM-issue-*&lt;/code&gt;. The job itself runs on Ubuntu, and starts by checking out the code in the repository into the GitHub Action runner.&lt;/p&gt;

&lt;p&gt;It then installs and sets up a Java installation using the AdoptOpenJDK distribution on the runner. The choice of Java version is chosen dynamically. If the commit message contains "Code transformation completed", Java version 17 is installed, else Java 11.&lt;/p&gt;

&lt;p&gt;The script then uses &lt;code&gt;maven&lt;/code&gt; commands to clean and rebuild the project, builds the projects and runs all tests, and finally copies all project dependencies into a specific folder. These are then uploaded as a project artifact from the runner machine into GitHub.&lt;/p&gt;

&lt;h2&gt;
  
  
  Triggering the Code Transformation
&lt;/h2&gt;

&lt;p&gt;Triggering the Java code transformation is as simple as raising a GitHub issue, and applying the Amazon Q transform agent label to the issue.&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%2Fxvbeeu4akl3huyb4yve3.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%2Fxvbeeu4akl3huyb4yve3.png" alt="Create GitHub Issue" width="800" height="387"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;amazon-q-developer&lt;/code&gt; application then takes over, adding comments to the issue to keep you up to date. It starts by running the GitHub Actions workflow required to transform the code. You can view the progress of the runner in the Actions 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%2Fy2h6l9zw0l3cvxkjanb2.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%2Fy2h6l9zw0l3cvxkjanb2.png" alt="GitHub Action Runner" width="800" height="299"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once successful, your code to be transformed is uploaded, and a transformation plan created. This code transformation plan sets out the changes that the agent is initially expecting to apply.&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%2Fgkxj5ich0gbo2ef5b7uc.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%2Fgkxj5ich0gbo2ef5b7uc.png" alt="Code Transformation Plan" width="774" height="542"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The agent then starts upgrading the code to Java 17. Once complete, the agent updates the issue with a comment and opens a pull request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Viewing the transformed code
&lt;/h2&gt;

&lt;p&gt;The pull request opened by the agent starts off with a code transformation summary detailing the changes made during the transformation process.&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%2F72j85y846iksebh71u20.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%2F72j85y846iksebh71u20.png" alt="Code Transformation Summary Changes" width="744" height="219"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One area that the transform agent has significantly improved over the past few months is in summarising these changes. Not only are these nicely laid out in a list format, but for each significant change, there are details provided why it has been made and the benefits it offers.&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%2Ffbbdr2pu07lt08qurmp4.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%2Ffbbdr2pu07lt08qurmp4.png" alt="Summary of Changes" width="749" height="665"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Security vulnerabilities and code quality issues
&lt;/h2&gt;

&lt;p&gt;One benefit that the GitHub integration brings is the scanning of all pull requests for security vulnerabilities and code quality issues. The application will raise a comment in the pull request, and then highlight any findings it discovers. In my case, the application generates a high severity warning.&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%2F26el17y9ht54vpjxl9vu.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%2F26el17y9ht54vpjxl9vu.png" alt="Security Vulnerabilities" width="800" height="314"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For each vulnerability found, a detailed description of the fix needed alongside code that can be committed is provided.&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%2Fzjnj7wuk91i15yp526nr.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%2Fzjnj7wuk91i15yp526nr.png" alt="Security Vulnerability Fix" width="800" height="306"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If multiple issues are discovered, you can go into the &lt;code&gt;Files Changed&lt;/code&gt; and select to batch up all of the suggestions into a single commit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Observations and Conclusion
&lt;/h2&gt;

&lt;p&gt;There are a few areas that are worth drawing out that initially caught me out.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Supported Target Code Versions&lt;br&gt;
The transform agent in the IDE currently only supports Java 17. The transform agent in the IDE has recently enabled support for Java 21 as a target code version as well as Java 17.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Code Review&lt;br&gt;
The code review run on the pull request is only carried out against the changes made in the diff, and not against unchanged content in these files, or the entire repo. To do this, you will need to go back to the IDE and run the &lt;code&gt;/review&lt;/code&gt; agent. It would be great to trigger a full code review on an entire repo through the GitHub integration&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Code Suggestions for Security Vulnerabilities&lt;br&gt;
Ironically, as the code review is only carried out against new changes, the vulnerability detected by the agent is against code that the agent itself created. I'm not entirely sure how I'm supposed to feel about this. In fairness, its most likely a reflection of the wider codebase and the code suggestion following the existing styling. In addition, the code suggestion made to resolve the security vulnerability which I automatically accepted failed compilation. This was detected almost straight away as it triggered another run of the GitHub Action. It turned out simpler to fix this compilation error manually and add this as a commit to the pull request. I'm not sure why this was the case, but I feel confident this will be fixed soon.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For Java transformations this now provides an alternative approach to using the agent in the IDE. Using the transform agent in the IDE feels more synchronous, as you watch the progress taking place in the Transformation Hub terminal. I really enjoyed the asynchronous nature of the GitHub integration. I can simply create a new issue, use a label to assign to the transform agent, and then wait for the email notification that it is complete. This frees up time to carry on with other value add activities.&lt;/p&gt;

&lt;p&gt;Running the transform agent in the IDE, you are also responsible for creating a separate branch, committing the changes to this branch, and raising the PR, all of which is taken care of for you with the GitHub integration. Even better, once merged into the main branch, the full conversation history is still maintained in the closed Pull Request for transparency.&lt;/p&gt;

&lt;p&gt;The project I used can be found here - &lt;a href="https://github.com/mlewis7127/bicycle-licence-GH-integration" rel="noopener noreferrer"&gt;https://github.com/mlewis7127/bicycle-licence-GH-integration&lt;/a&gt;. If you have a requirement to transform old Java code projects, I would definitely recommend checking out this integration, and see how it fits into your developer workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Biography
&lt;/h2&gt;

&lt;p&gt;As Chief AWS Architect at IBM in the UK, I am responsible for growing the AWS capability and community within one of the fastest growing AWS consulting partners globally. This often gives me the opportunity to try out the latest features in preview before they go into general availability. You'll often find me blogging about my experience, but please reach out if there are services you'd like to know more about.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>tutorial</category>
      <category>aws</category>
    </item>
    <item>
      <title>Amazon Q Developer transform for .NET</title>
      <dc:creator>Matt Lewis</dc:creator>
      <pubDate>Mon, 20 Jan 2025 10:19:13 +0000</pubDate>
      <link>https://dev.to/aws-heroes/amazon-q-developer-transform-for-net-5c98</link>
      <guid>https://dev.to/aws-heroes/amazon-q-developer-transform-for-net-5c98</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;A fantastic use case for AI Coding Assistants is upgrading applications to modern and supported versions of programming languages, libraries and frameworks. All too often, engineering effort is spent building new features, whilst existing applications are left untouched, until they become unsupported with all kinds of inherent vulnerabilities.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Amazon Q Developer&lt;/code&gt; introduced a code transformation agent for Java when launched in preview back in November 2023. This agent has undergone multiple iterations and become more accurate and powerful since then. On 3rd December 2024 at AWS re:Invent, the public preview of new transformation capabilities for .NET, mainframe, and VMware workloads was announced.&lt;/p&gt;

&lt;p&gt;In this blog post, I take a look at the .NET transformation capability to get a better understanding of how it works. This feature is available in the &lt;code&gt;Visual Studio IDE&lt;/code&gt;. However, &lt;code&gt;Visual Studio for Mac&lt;/code&gt; has been retired, which gave me an opportunity to try out the Amazon Q Developer transformation web experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why port .NET Framework?
&lt;/h2&gt;

&lt;p&gt;.NET Framework is the original implementation of .NET by Microsoft. It supports running websites, services, desktop apps and more, but only on Windows.&lt;/p&gt;

&lt;p&gt;.NET (sometimes called .NET Core) is a more modern, open-source version of .NET that can run on multiple operating systems including Windows, Linux and MacOS. The reasons to continue using .NET Framework are specific and limited according to Microsoft, and relate to use cases where your application is using third-party libraries or NuGet packages or .NET Framework technologies that are not available for .NET.&lt;/p&gt;

&lt;p&gt;Where possible you should look to utilise .NET.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started with web experience
&lt;/h2&gt;

&lt;p&gt;To get started with the web experience, I had to subscribe to &lt;code&gt;Amazon Q Developer&lt;/code&gt; from your management (root) account, as it is not currently possible to use a delegated administrator account. Note that you need to be in the &lt;code&gt;us-east-1&lt;/code&gt; region at this point.&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%2Fzkuthgu1ah2b962wsgnb.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%2Fzkuthgu1ah2b962wsgnb.png" alt="Amazon Q Developer Start Page" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This takes you to a &lt;code&gt;Getting started with Amazon Q&lt;/code&gt; page. I was prompted to switch back to my home region (&lt;code&gt;eu-west-2&lt;/code&gt;) which then automatically connected my &lt;code&gt;AWS Organization&lt;/code&gt; instance of &lt;code&gt;IAM Identity Center&lt;/code&gt; to &lt;code&gt;Amazon Q&lt;/code&gt;. At this point, I clicked the button to "subscribe" and added a user from &lt;code&gt;IAM Identity Center&lt;/code&gt;. It is also possible to add a Group instead of an individual user or users.&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%2Fu34d1wcqn9bdk9rfrpiv.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%2Fu34d1wcqn9bdk9rfrpiv.png" alt="Connect to IAM Identity Center" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This successfully created an &lt;code&gt;Amazon Q Developer&lt;/code&gt; Pro subscription for my chosen user. At this point, I clicked on the button which took me to the &lt;code&gt;Amazon Q Developer&lt;/code&gt; console to complete the setup.&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%2F1eyese8uxyhn8furwzv3.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%2F1eyese8uxyhn8furwzv3.png" alt="Create an Amazon Q Subscription" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Opening up &lt;code&gt;Amazon Q Developer&lt;/code&gt; in the AWS Console gave the option to click on a settings button.&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%2Fxh8ndltjpzpb9hdeu37r.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%2Fxh8ndltjpzpb9hdeu37r.png" alt="Amazon Q Developer Console Settings" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the settings, I enabled the transform settings, which is required to give access to the transformation web experience.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnc33ddi92qhh6z0ft0i9.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%2Fnc33ddi92qhh6z0ft0i9.png" alt="Enable Amazon Q Transform Settings" width="800" height="164"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At this point, I navigated in a browser window to &lt;a href="https://transform.developer.q.aws.com/" rel="noopener noreferrer"&gt;https://transform.developer.q.aws.com/&lt;/a&gt; and signed in using &lt;code&gt;IAM Identity Centre&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdyufggmv4gm6tvp5ojqc.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%2Fdyufggmv4gm6tvp5ojqc.png" alt="Sign in using IAM Identity Center" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once logged in, I was presented with the option of creating my first transformation job with &lt;code&gt;Amazon Q&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2gqyvh8wuvht95jyz21q.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%2F2gqyvh8wuvht95jyz21q.png" alt="Create first transformation job with Amazon Q" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Running transformation for .NET
&lt;/h2&gt;

&lt;p&gt;Once I asked Q to create a transformation job, I was given the choice  of the type of transformation to work on. There are three options available:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Modernize .NET applications to cross-platform .NET&lt;/li&gt;
&lt;li&gt;Migrate VMware applications to Amazon EC2&lt;/li&gt;
&lt;li&gt;Perform mainframe modernization (z/os to AWS)&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%2Fdqrbicb9sh09z0j2fmu9.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%2Fdqrbicb9sh09z0j2fmu9.png" alt="Choose transformation type" width="800" height="138"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I chose the option to modernise a .NET application. &lt;code&gt;Amazon Q&lt;/code&gt; then populated a number of details about the .NET modernisation project. I could change these details, or in this case, confirm they are correct and let &lt;code&gt;Amazon Q&lt;/code&gt; create the job itself.&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%2Fdcp7ijz0t9t3fhw8dkfx.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%2Fdcp7ijz0t9t3fhw8dkfx.png" alt="Confirm .NET transformation job" width="800" height="219"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At this point I had to connect &lt;code&gt;Amazon Q&lt;/code&gt; to my source repository for the project I want to transform. I have forked the &lt;a href="https://github.com/leitosama/SharpZeroLogon" rel="noopener noreferrer"&gt;SharpZeroLogon&lt;/a&gt; GitHub repository to my own profile. This is an archived repository that was a rework of the NCC Group's tool specifically for .NET Framework 3.5.&lt;/p&gt;

&lt;p&gt;The connection is made using &lt;code&gt;AWS CodeConnections&lt;/code&gt;. Within an AWS account, you use &lt;code&gt;CodeConnections&lt;/code&gt; to create a connection to a third party Git-based source provider. Currently, the only supported provider is GitHub. To create a connection, you need to go to &lt;code&gt;AWS CodeArtifact&lt;/code&gt;, click on &lt;code&gt;Settings&lt;/code&gt; and then &lt;code&gt;Connections&lt;/code&gt;. I am using GitHub which installs the &lt;code&gt;AWS Connector for GitHub&lt;/code&gt; as an application in GitHub. You can configure the connector with access to only specific repositories.&lt;/p&gt;

&lt;p&gt;To setup the Amazon Q transformation job you first specify the account number for the AWS account where the connection is 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%2Fhg1h1aafsawo6cvc2lm9.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%2Fhg1h1aafsawo6cvc2lm9.png" alt="Select AWS Account ID" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You then specify the &lt;code&gt;AWS CodeConnection&lt;/code&gt; 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%2Fcqdiroy5nof01avm8jpv.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%2Fcqdiroy5nof01avm8jpv.png" alt="AWS CodeConnection ARN" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You then go back into the AWS console to approve this connection request.&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%2Fxc1g3ycjfwkf645auio4.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%2Fxc1g3ycjfwkf645auio4.png" alt="Approve Connection Request" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the connection request has been approved, you click on &lt;code&gt;Send to Q&lt;/code&gt;, which will allow &lt;code&gt;Amazon Q&lt;/code&gt; to access the repositories in the connected account.&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%2Fk56ptf6nz5646tcrj8z7.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%2Fk56ptf6nz5646tcrj8z7.png" alt="Create Connector Send to Amazon Q" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Amazon Q&lt;/code&gt; analyses all of the repositories it has access too, to discover which ones run a .NET Framework application that is capable of being transformed. &lt;code&gt;Amazon Q Developer&lt;/code&gt; transformation capabilities for .NET supports porting C# code projects of the following types: console application, class library, unit tests, web API, Windows Communication Foundation (WCF) service, and business logic layers of Model View Controller (MVC) and Single Page Application (SPA). Types of jobs that &lt;code&gt;Amazon Q&lt;/code&gt; currently cannot transform include WebUI, SQLServer and ASP.NET.&lt;/p&gt;

&lt;p&gt;In my example, the &lt;code&gt;SharpZeroLogin&lt;/code&gt; repository has been detected as a supported project, and I am given the option to specify a target version (.NET 8.0). I can also specify the name of the new branch that will be created or keep the default.&lt;/p&gt;

&lt;p&gt;Note that the web experience gives you the option of carrying out a .NET transform of multiple repositories. This is something not available within the IDE, which only allows one .NET solution at a time.&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%2F8a1zi97degiu30zwvinc.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%2F8a1zi97degiu30zwvinc.png" alt="Confirm Repository to Transform" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Amazon Q&lt;/code&gt; now automatically ports the selected .NET application to the target version following a transformation plan it has created. It commits all of the transformed code to a new branch in my GitHub repository, preserving the original source code.&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%2F6lae5ijly53zpkmdt02a.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%2F6lae5ijly53zpkmdt02a.png" alt="Job Completed Successfully" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I can click on the Dashboard tab to monitor the progress. In this case, I am told that the application has been transformed with no issues.&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%2Fmts7vdavps0l348pvqx6.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%2Fmts7vdavps0l348pvqx6.png" alt="Job Completed Dashboard" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I can now go to my GitHub repository and look at the new branch that has been created. I can also view the diffs to see what changes have been made. In the file below, we can see that the target framework version has been updated from "3.5" to "net8.0".&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%2F2ftey1kf583vh0a3crfx.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%2F2ftey1kf583vh0a3crfx.png" alt="View Code Diffs" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;The goal of this blog post is to show you how to simple it is to get up and running with the new &lt;code&gt;Amazon Q Developer&lt;/code&gt; transformation web experience. If you have existing .NET Framework applications that you want to port to .NET to gain performance improvements and cross-platform support, it is definitely worth giving this feature a go.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>tutorial</category>
      <category>ai</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>Amazon GuardDuty Extended Threat Detection</title>
      <dc:creator>Matt Lewis</dc:creator>
      <pubDate>Mon, 02 Dec 2024 15:19:53 +0000</pubDate>
      <link>https://dev.to/aws-heroes/amazon-guardduty-extended-threat-detection-3l72</link>
      <guid>https://dev.to/aws-heroes/amazon-guardduty-extended-threat-detection-3l72</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;I was lucky to get the opportunity to try out the new "Extended Threat Protection" feature for &lt;code&gt;Amazon GuardDuty&lt;/code&gt; whilst in beta. With the announcement of this new feature, I wanted to share more around my experience, and the value this new feature brings. Before jumping into this, let's start by providing some background to &lt;code&gt;Amazon GuardDuty&lt;/code&gt; and the benefits it provides, to those who may not be familiar with the service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Amazon GuardDuty?
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Amazon GuardDuty&lt;/code&gt; is a threat detection service that continuously monitors, analyses and processes AWS logs and other data sources for malicious and abnormal activity. It uses its own internal feeds, alongside other intelligence feeds from CrowdStrike and Proofpoint to detect the latest threats and attack techniques. As someone who has worked for many years in heavily regulated industries processing sensitive data sets in areas of critical national infrastructure, I have been a huge advocate of &lt;code&gt;Amazon GuardDuty&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In modern cloud environments, the quantity of logs and events that is captured is enormous. When it comes to threat detection, you require real-time and accurate visibility into this data. When your workloads reside on AWS, shipping this data externally to another cloud provider or back on-premises adds significant egress costs and latency. This is why I always look to use GuardDuty, so the data can be analysed at source, and threat detection can be consumed as a managed service.&lt;/p&gt;

&lt;p&gt;GuardDuty uses a baseline of foundational data sources, and processes these logs using independent streams of data so it does not affect existing configurations. These foundational data sources are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AWS CloudTrail&lt;/strong&gt; - showing a history of AWS API calls and management events.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;VPC Flow Logs&lt;/strong&gt; - showing details of IP traffic going to and from network interfaces attached to your EC2 instances.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Route 53 Resolver DNS logs&lt;/strong&gt; - showing a history of DNS queries.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On top of this baseline, you have the option to enable protection plans, which are specialised features within GuardDuty that provide enhanced threat detection for specific AWS services, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;S3 Protection&lt;/strong&gt; - helps detect risks such as data exfiltration and destruction in your S3 buckets.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;EKS Protection&lt;/strong&gt; - monitors EKS audit logs to identify potential security issues such as unauthenticated actor attempts to collect secrets or AWS credentials, and suspicious container deployments with images not commonly used in the cluster.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Runtime Monitoring&lt;/strong&gt; - observes and analyses operating-system level, networking, and file events to help detect potential threats for EC2 instances and container workloads in EKS and ECS including Fargate.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Malware Protection for EC2&lt;/strong&gt; - detect the potential presence of malware by scanning the EBS volumes attached to EC2 instances.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Malware Protection for S3&lt;/strong&gt; - detect the potential presence or malware by scanning newly uploaded objects to selected S3 buckets.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;RDS Protection&lt;/strong&gt; - profile and monitor access activity to Aurora databases in your AWS account without impacting database performance, to detect potential threats such as high severity brute force attacks, suspicious logins, and access by known threat actors.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Lambda Protection&lt;/strong&gt; - identifies potential security threats when an AWS Lambda gets invoked in your AWS environment by monitoring Lambda network activity logs.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Integration with AWS Services
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Amazon GuardDuty&lt;/code&gt; is tightly integrated with other AWS services to enable fast responses.&lt;/p&gt;

&lt;h3&gt;
  
  
  Amazon Detective
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Amazon Detective&lt;/code&gt; ingests GuardDuty findings and allows you to quickly analyse and investigate these events.&lt;/p&gt;

&lt;h3&gt;
  
  
  AWS Security Hub
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;AWS Security Hub&lt;/code&gt; is a cloud security posture management (CSPM) service. It collects findings from the security services enabled across your AWS accounts, such as intrusion detection findings from GuardDuty, vulnerability scans from Inspector, and sensitive data identification findings from Macie. It runs continuous and automated account and resource-level configuration checks against the controls in the AWS Foundational Security Best Practices standard and other supported industry best practices and standards such as NIST and PCI DSS. The screenshot below shows GuardDuty findings in Security Hub.&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%2Fd33f44kj8crcprmw1cbv.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%2Fd33f44kj8crcprmw1cbv.png" alt="Security Hub Findings"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Amazon EventBridge
&lt;/h3&gt;

&lt;p&gt;GuardDuty creates an event whenever a new finding occurs. These are routed to the default event bus in &lt;code&gt;Amazon EventBridge&lt;/code&gt;. You can configure an EventBridge rule with a pattern that listens for GuardDuty findings in order to automatically respond to these events.&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%2Fkzucsywb8vdtis10lchc.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%2Fkzucsywb8vdtis10lchc.png" alt="Amazon EventBridge Rule"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Common use cases include sending automatic alerts for high severity findings, or automating remediation (e.g. disabling a compromised access key)&lt;/p&gt;

&lt;h2&gt;
  
  
  Extended Threat Detection with Attack Sequences
&lt;/h2&gt;

&lt;p&gt;GuardDuty Extended Threat Detection is a new feature of &lt;code&gt;Amazon GuardDuty&lt;/code&gt; that uniquely identifies attack sequences spanning multiple AWS data sources and resources within a 24-hour time window within an AWS account.&lt;/p&gt;

&lt;p&gt;This addresses the risk where an attack could be comprised of a number of related suspicious activities over a period of time. Each of these suspicious activities may generate their own individual finding. However, these may be of a lower severity and act as a weak signal, and so not seen as presenting a real threat. However, when these weak signals are considered together, and the sequence of these activities align to a more suspicious activity, GuardDuty will generate an attack sequence finding.&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%2Ftm646c21idsquxy1x47i.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%2Ftm646c21idsquxy1x47i.png" alt="GuardDuty Summary with Attack Sequence"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this case, we have triggered a finding of type &lt;code&gt;&lt;br&gt;
AttackSequence:IAM/CompromisedCredentials&lt;/code&gt;. We can see looking at the summary of findings that this has been given a critical severity level.&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%2Fprbeyk1ahsj5ung7agxl.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%2Fprbeyk1ahsj5ung7agxl.png" alt="GuardDuty Findings"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Clicking into the finding and selection "View details" brings up the overview page. This provides a compact view of the attack sequence details, including signals, MITRE tactics, and potentially impacted resources. In the screenshot below, (1) shows the signals, (2) shows the MITRE tactics, and (3) shows the indicators.&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%2Ffk38baspm2oaprjfbonv.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%2Ffk38baspm2oaprjfbonv.png" alt="Attack Sequence Overview Page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Signals displays a timeline of events that are involved in the attack sequence. Each individual signal could be an API activity or finding that GuardDuty used to detect the attack sequence. Each signal, that is a GuardDuty finding, has it's own severity level and value assigned to it. In the GuardDuty console, you can select each signal to view the associated details.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjt5h8y7toa2bzqh6tn4h.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%2Fjt5h8y7toa2bzqh6tn4h.png" alt="Signals Timeline"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One of my favourite aspects of the new feature is the mapping of the finding to both MITRE ATT&amp;amp;CK(™️) tactics and techniques. This "compromised credentials" attack sequence was comprised of the following 3 MITRE ATT&amp;amp;CK tactics. GuardDuty uses the MITRE ATT&amp;amp;CK framework to add context to the entire attack sequence. The colours GuardDuty uses to specify the threat purposes used by the threat actor, align with the colours that indicate the critical, high, medium, and low findings severity level.&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%2F568k1i6tt4xx9689o11m.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%2F568k1i6tt4xx9689o11m.png" alt="MITRE tactics"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The indicators section shows observed data that matches the pattern of a security issue, and is the reason why this collection of signals was identified as an attack sequence. For example, the "High risk API" indicator is flagged as the &lt;code&gt;cloudtrail:DeleteTrail&lt;/code&gt; and &lt;code&gt;iam:CreateUser&lt;/code&gt; API calls were made, which are actions commonly used by threat actors.&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%2F19r09drugg5rwf1g3tch.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%2F19r09drugg5rwf1g3tch.png" alt="Indicators"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I setup a rule in EventBridge to capture an attack sequence finding. A small subset of the JSON event message is shown below. This message also provides details of the associated signals.&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%2F1b496q230gxxbqktusxw.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%2F1b496q230gxxbqktusxw.png" alt="Sample GuardDuty Finding"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Overall, this is a fantastic new feature in GuardDuty and I am excited to see more attack sequence detections being added over time.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>security</category>
      <category>aws</category>
    </item>
    <item>
      <title>Accelerating builds with Amazon Q Developer Agent</title>
      <dc:creator>Matt Lewis</dc:creator>
      <pubDate>Thu, 21 Nov 2024 12:07:45 +0000</pubDate>
      <link>https://dev.to/aws-heroes/accelerating-builds-with-amazon-q-developer-agent-2dd5</link>
      <guid>https://dev.to/aws-heroes/accelerating-builds-with-amazon-q-developer-agent-2dd5</guid>
      <description>&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;I have been using the &lt;code&gt;Amazon Q Developer Agent for software development&lt;/code&gt; for some time, looking at ways to get the agent to consistently generate more accurate output. This has highlighted the importance of the prompt, as the primary way of instructing the agent about the type and style of content to be generated. I have found the more precise guidance the prompt contains, the better the output. However, crafting these prompts can be time consuming. This leads to another challenge of ensuring that prompts are used in a consistent fashion across all engineers in a team.&lt;/p&gt;

&lt;p&gt;In this post we look at a simple way of introducing “prompt templates” as a better way of ensuring the accuracy of content generated. The goal is to generate a complete project using a number of AWS services written in Python, with the infrastructure managed and provisioned by Terraform. This is being carried out with limited knowledge of these languages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bootstrapping our project
&lt;/h2&gt;

&lt;p&gt;We start off by bootstrapping the project with just 2 files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;requirements.md&lt;/code&gt; - a markdown file containing our requirements in terms of programming languages and other guidance. This acts as our prompt template&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;architecture.puml&lt;/code&gt; - a Plant UML diagram with the design of the application we want to build&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%2Fnh0gteqmhwsrjvd4mz97.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%2Fnh0gteqmhwsrjvd4mz97.png" alt="Initial Project Setup" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From here, we can now pass in a simple prompt to the &lt;code&gt;Amazon Q Developer Agent&lt;/code&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Create a complete project implementation including source code, unit tests and a requirements.txt file that meets all of the project requirements as set out in the requirements.md file. Use Terraform following standard best practice to define and deploy all services as detailed in the Plant UML diagram format below:”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I have not found an accurate way of getting Amazon Q Developer to recognise the &lt;code&gt;.puml&lt;/code&gt; file, so for the moment, I copy and paste the Plant UML diagram content into the prompt:&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%2Fppxw8bvet4hdqcy0wjae.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%2Fppxw8bvet4hdqcy0wjae.png" alt="Project Prompt" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Amazon Q Developer Agent for software development&lt;/code&gt; plans out all the steps it needs to take to meet the ask. In the very first step, the agent states:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I need to open requirements.md to review the project requirements. Based on Plant UML diagram, I can see it's an AWS serverless architecture with an API Gateway, Lambda functions and DynamoDB. I'll first create the necessary project structure&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The complete summary of changes are shown in the screenshot below. It is interesting to see the agent is clever enough to recognise that some of the imports are not working correctly, and it continually iterates after each change, until it is confident that the requirements have been met.&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%2Fcusy3957smk9jxom2sgq.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%2Fcusy3957smk9jxom2sgq.png" alt="Summary of Changes" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I accept the proposed code changes, and now I have a full project structure that has been created for me, with a &lt;code&gt;README.md&lt;/code&gt; file that gives me more details about the project.&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%2Felt98uh5s8ny2e9u41wm.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%2Felt98uh5s8ny2e9u41wm.png" alt="Project Files" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Running the application
&lt;/h2&gt;

&lt;p&gt;Next we want to get the application up and running. I recorded a video to show this working. There were no code changes needed. I just needed to execute a python script to package up the source code for each Lambda function into a Zip file. I also used the inline chat functionality of &lt;code&gt;Amazon Q Developer&lt;/code&gt; to output the API endpoint URL so I could run some cURL commands.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/v9roxvkZQR8"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Design versus Build Comparison
&lt;/h2&gt;

&lt;p&gt;The only details of what AWS services that made up the application were shown in the Plant UML diagram. What I found most impressive was how accurately the software development agent created the infrastructure to match the design.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The design shows 4 individual AWS Lambda functions called CreateItem, ReadItem, UpdateItem and DeleteItem. These are the exact names of the Lambda functions created.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The design shows the HTTP verbs (POST, GET, DELETE) and the URL Path Notation. These have been created in API Gateway exactly as they are shown in the diagram.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The design shows a DynamoDB table called Item with a partition key of &lt;code&gt;id&lt;/code&gt;. This has also been created exactly as shown in the diagram&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw5eoehyp3h4p00hh29ol.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%2Fw5eoehyp3h4p00hh29ol.png" alt="Design vs Build Comparison" width="800" height="433"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In addition, we can see that the code generated conforms to the specification set out in the requirements file, being written in Python with headers and comments and meaningful variable names.&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%2Fhymlwksbbnysas315v28.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%2Fhymlwksbbnysas315v28.png" alt="Create Item Lambda Function" width="800" height="547"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;I am really excited to see the potential that exists today with the approach taken here. I love seeing how visual design artefacts can be taken and converted directly into the underlying infrastructure on AWS. This helps to provide a seamless transition between design and build. It also solves another challenge of adopting a prompt template that can be used consistently by engineers within a team and across teams in an organisation. This approach allows for the consistent generation of code artefacts that meet organisation guidelines, by ensuring that project repositories are bootstrapped when created with a standard requirements file.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>genai</category>
      <category>tutorial</category>
      <category>terraform</category>
    </item>
    <item>
      <title>Amazon Q Developer Agent for Code Transformation</title>
      <dc:creator>Matt Lewis</dc:creator>
      <pubDate>Mon, 18 Nov 2024 09:56:30 +0000</pubDate>
      <link>https://dev.to/aws-heroes/amazon-q-developer-agent-for-code-transformation-1bd0</link>
      <guid>https://dev.to/aws-heroes/amazon-q-developer-agent-for-code-transformation-1bd0</guid>
      <description>&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;Here in the UK, the &lt;a href="https://www.ncsc.gov.uk/" rel="noopener noreferrer"&gt;National Cyber Security Centre (NCSC)&lt;/a&gt; is the 'technical authority' for cyber incidents with a view that:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;patching remains the single most important thing you can do to secure your technology, and is why applying patches is often described as 'doing the basics'&lt;/strong&gt; &lt;a href="https://www.ncsc.gov.uk/blog-post/the-problems-with-patching" rel="noopener noreferrer"&gt;blog post&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;All too often, all engineering effort is spent building new features, and existing applications are left untouched, until they are running out of date versions of libraries and frameworks with all kinds of inherent vulnerabilities. This is where the &lt;code&gt;Amazon Q Developer Agent for Code Transformation&lt;/code&gt; comes into its own. In this article, we take the agent for a spin looking to upgrade a Java 11 application to Java 17.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bicycle Licence Application
&lt;/h2&gt;

&lt;p&gt;In March 2020, I presented an online AWS tech talk demonstrating features of &lt;code&gt;Amazon QLDB&lt;/code&gt; in an application written in Java 11 and Spring Boot. In the past couple of months, I have replaced &lt;code&gt;Amazon QLDB&lt;/code&gt; with &lt;code&gt;Amazon DynamoDB&lt;/code&gt;, and made sure that it is simple to start up and run as a Java 11 application. This is now a great project to test out the code transformation agent. It is a more complex application with around 750 lines of code than a basic "Hello World" application, but not so large it is difficult to understand. You can clone this project yourself &lt;a href="https://github.com/mlewis7127/bicycle-licence-ui-master" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up the Java 11 application
&lt;/h2&gt;

&lt;p&gt;All screenshots are taken using the &lt;code&gt;Intellij IDEA&lt;/code&gt;. The first step is to clone the repository and open as a new project. Make sure the project structure is setup to use version 11 of the Java SDK:&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%2Fcnqsd7qotpdjjtcdk2et.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%2Fcnqsd7qotpdjjtcdk2et.png" alt="Project Structure Java 11" width="800" height="354"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The application requires a DynamoDB table with a Global Secondary Index to be setup in the &lt;code&gt;eu-west-1&lt;/code&gt; region, and there is a CloudFormation template provided you can use to set this up. I couldn't remember the exact command to use, but luckily &lt;code&gt;Amazon Q on the command line&lt;/code&gt; came to the rescue.&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%2Famk70cx5752l58l4rndd.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%2Famk70cx5752l58l4rndd.png" alt="Amazon Q Developer on the command line" width="800" height="205"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Run a &lt;code&gt;mvn clean&lt;/code&gt; and then a &lt;code&gt;mvn compile&lt;/code&gt;, which will compile all of the code successfully. You can then launch the application using a new Spring Boot 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%2Fn628wsmtq8l0vmc4tih1.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%2Fn628wsmtq8l0vmc4tih1.png" alt="Spring Boot configuration" width="800" height="433"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a new Bicycle Licence record
&lt;/h2&gt;

&lt;p&gt;Once launched, the application will be running at &lt;code&gt;http://localhost:8080/&lt;/code&gt;. Opening this in a browser window will render the landing page for the application:&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%2Fibjn80h6f7w21k91dywx.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%2Fibjn80h6f7w21k91dywx.png" alt="Bicycle Licence UI" width="612" height="344"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From here, you can start by creating a new fictitious bicycle licence by specifying a name, telephone number and email address:&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%2Fhxv02f21vvyorkjrl1yj.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%2Fhxv02f21vvyorkjrl1yj.png" alt="Bicycle Licence UI Create" width="612" height="344"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once created, you can go into the view/update licence tab and add some points to the licence:&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%2F2ygj88acpjgtz5b45w7r.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%2F2ygj88acpjgtz5b45w7r.png" alt="Bicycle Licence UI Update" width="612" height="344"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, you can click in the history tab and view all the events that have taken place against that record:&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%2Fvm5unzyio53d5jrk4576.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%2Fvm5unzyio53d5jrk4576.png" alt="Bicycle Licence UI History" width="612" height="344"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we have this up and running as a Java 11 application, we want to look to migrate to Java 17.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running the code transformation agent
&lt;/h2&gt;

&lt;p&gt;The first step in running a code transformation is to type &lt;code&gt;/transform&lt;/code&gt; into the &lt;code&gt;Amazon Q&lt;/code&gt; chat window. &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%2Fysplixg0ljfycq42621a.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%2Fysplixg0ljfycq42621a.png" alt="Transform" width="612" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From here, &lt;code&gt;Amazon Q&lt;/code&gt; will analyse the open workspace to identify if there is a module available running Java 8 or Java 11 that can be transformed. It will automatically recognise the &lt;code&gt;bicycle-licence-ui&lt;/code&gt; module, and pre-select this for you. We can simply confirm we want to transform this module to JDK17.&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%2F3x9l0x0f7ayhg54eap2g.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%2F3x9l0x0f7ayhg54eap2g.png" alt="Welcome to Code Transformation" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is also an option for the agent to build your module with or without running unit tests. The default is to run tests, and a number of unit tests are included as part of the project to show this feature working.&lt;/p&gt;

&lt;p&gt;The first step is that &lt;code&gt;Amazon Q&lt;/code&gt; builds your module locally, downloading all dependencies, and then running the unit tests. Assuming this is successful, &lt;code&gt;Amazon Q&lt;/code&gt; will then scan the project files and get ready to start the job. To do this, the project artifacts are uploaded to a managed secure build environment on AWS.&lt;/p&gt;

&lt;p&gt;Once the files have been uploaded, the transformation job has been accepted and is ready to start. The application is built again using Java 11. Following this, the code is analysed in order to generate a transformation 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%2Ff0ign2vkisr3qc7utww0.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%2Ff0ign2vkisr3qc7utww0.png" alt="Code Transformation Plan" width="612" height="506"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the plan used by the agent to transform your code. The whole concept of an agent is that it can run autonomously to complete a complex task, and take actions based upon its findings as it progresses, without direct human intervention. The agent can make use of RAG and custom models, which are all abstracted away from the end user. It does mean that the final code updates may end up being slightly different than the initial plan, as the agent will continue to re-evaluate its progress after each step.&lt;/p&gt;

&lt;p&gt;The most impressive feature for me is watching the agent apply the updated dependencies and code changes, and then building the module in a Java 17 environment. You can see from the screenshot below that each time updated dependencies were added, the application failed to compile. When this took place, the agent is able to access other underlying models to work out what further changes are required, until eventually the application can now be built successfully in Java 17.&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%2Fr5j9xb2l1raew8xhaqrd.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%2Fr5j9xb2l1raew8xhaqrd.png" alt="Code Transformation Java 17 Build" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After just over 12 minutes, the transformation job was complete, and it was time to review the code diff and see the proposed changes:&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%2Fff20woq5qmxsgap7ugo1.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%2Fff20woq5qmxsgap7ugo1.png" alt="Code Transformation Java 17 Complete" width="612" height="344"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Clicking "view diff" opens up a new window highlighting the files that have been modified:&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%2Fz882egu68ws0fgqgugvy.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%2Fz882egu68ws0fgqgugvy.png" alt="View Diff" width="540" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can click on each one, to see these changes. In the example below, we can see that a new method has been added to the &lt;code&gt;BicycleLicenceDynamoDBRepository.java&lt;/code&gt; class. This is because the &lt;code&gt;CrudRepository&lt;/code&gt; interface provided by Spring Data that this class implements has had this new method added to it in the version upgrade.&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%2Fx4qa3xismfuisk87ji39.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%2Fx4qa3xismfuisk87ji39.png" alt="New Interface Method Added" width="800" height="488"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also view the Code Transformation Summary provided by Amazon Q. This provides details around how many lines of code were analysed, how many files have been changed, how many planned dependencies have been added and so on. It also provides a build log summary. In this case, I can see that all source files were successfully compiled and 6 tests ran without any failures, errors or skips.&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%2Fyeifrxh77hgb2f37jtjs.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%2Fyeifrxh77hgb2f37jtjs.png" alt="Code Transformation Summary" width="612" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We are now in a position where we can test the application after it has been transformed to Java 17. After accepting the code changes, we run an &lt;code&gt;mvn clean&lt;/code&gt; and &lt;code&gt;mvn compile&lt;/code&gt; and reload all project dependencies. It also involves making sure the project structure is setup to use version 17 of the Java SDK, alongside the Run Configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running the Java 17 application
&lt;/h2&gt;

&lt;p&gt;After making the previous changes, we run the application and can interact with the bicycle licence with no issues. However, one thing we notice is a warning message in the console that the &lt;code&gt;AWS SDK for Java 1.x&lt;/code&gt; has entered maintenance mode and will reach end of support on December 31, 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%2F49smvw8qa1zzvzxc1s7f.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%2F49smvw8qa1zzvzxc1s7f.png" alt="AWS SDK for Java 1.x" width="800" height="217"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In reality, it's a little disappointing that the AWS SDK library was not updated as part of the code transformation. The reality is that there are significant changes involved, and the AWS SDK for Java 2.x is a major rewrite of the 1.x code base. Out of curiosity, I wanted to see how the software development agent would handle this.&lt;/p&gt;

&lt;h2&gt;
  
  
  AWS SDK v1 to v2 upgrade with software development agent
&lt;/h2&gt;

&lt;p&gt;With the AWS SDK for Java v1 already in maintenance mode and not updated by the code transformation agent, I wanted to see how the software development agent would handle the upgrade. I typed in &lt;code&gt;\dev&lt;/code&gt; in the chat window, and entered the simple prompt of "Rewrite this application to use the AWS SDK for Java 2.x". The agent analysed the application and then worked through a whole set of changes&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%2Fpnmb7jbes7x5vkssercs.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%2Fpnmb7jbes7x5vkssercs.png" alt="Developer Agent Summary of Changes" width="612" height="288"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I really liked the fact that the developer agent created a migration plan which it then used iteratively to update all dependencies. This was a markdown file as shown 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%2Fw4kpte9bbvjkte94w2rf.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%2Fw4kpte9bbvjkte94w2rf.png" alt="Migration Plan" width="590" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Although the developer agent came up with a large number of correct changes, there were still some errors left behind. This really helped to highlight the differences between the two agents currently available. The two key points for me are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The software development agent does not allow you to test the generated code before it is accepted. This means that any issues such as compilation errors will need to be fixed manually (supported by the chat interface), or thrown back to the agent consuming another code generation from your quota&lt;/li&gt;
&lt;li&gt;The code transformation agent uploads your artifacts to a secure build environment, and will continually try to build and compile your code, fixing any errors as it goes along, until it is complete.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;The &lt;code&gt;Amazon Q Developer Agent for code transformation&lt;/code&gt; is a great example of the value that agents can bring, and why I believe they are the future for coding assistants. It handles the complex task of upgrading an application from an older version to a newer version autonomously, significantly reducing developer effort.&lt;/p&gt;

&lt;p&gt;The two main drawbacks I encountered are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The agent did not look to transform the outdated &lt;code&gt;AWS SDK for Java 1.x&lt;/code&gt; libraries to the latest &lt;code&gt;AWS SDK for Java 2.x&lt;/code&gt; libraries&lt;/li&gt;
&lt;li&gt;The target version is currently at Java 17. This is now outdated itself. &lt;code&gt;Amazon Corretto 17&lt;/code&gt; was released in September 2021, whilst &lt;code&gt;Amazon Corretto 21&lt;/code&gt; was released in September 2023 and &lt;code&gt;Amazon Corretto 23&lt;/code&gt; was released in September 2024, and hopefully we will see these more recent versions supported shortly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nevertheless, I really hope you try this agent out for yourself, and I'd love to hear your feedback.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>genai</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
