<?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: AWS Community Builders </title>
    <description>The latest articles on DEV Community by AWS Community Builders  (@aws-builders).</description>
    <link>https://dev.to/aws-builders</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%2Forganization%2Fprofile_image%2F2794%2F88da75b6-aadd-4ea1-8083-ae2dfca8be94.png</url>
      <title>DEV Community: AWS Community Builders </title>
      <link>https://dev.to/aws-builders</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aws-builders"/>
    <language>en</language>
    <item>
      <title>I Built a Bot That Updates My EKS Nodes While I Sleep — Here's How</title>
      <dc:creator>SURYANSH GUPTA</dc:creator>
      <pubDate>Sat, 30 May 2026 10:03:45 +0000</pubDate>
      <link>https://dev.to/aws-builders/i-built-a-bot-that-updates-my-eks-nodes-while-i-sleep-heres-how-2lgd</link>
      <guid>https://dev.to/aws-builders/i-built-a-bot-that-updates-my-eks-nodes-while-i-sleep-heres-how-2lgd</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Manual EKS AMI updates are slow, risky, and easy to forget. I wired together EventBridge, Lambda, Amazon Bedrock (Claude 3.5 Haiku), GitHub PRs, ArgoCD, and Karpenter into a pipeline that detects new AMIs, runs AI risk analysis, opens a PR for human review, and rolls out nodes automatically — zero downtime, full audit trail.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The problem every EKS team hits eventually
&lt;/h2&gt;

&lt;p&gt;You're running production Kubernetes on AWS. You know you're supposed to keep worker nodes patched. But between sprints, incidents, and everything else — checking for new EKS-optimized AMIs falls through the cracks.&lt;/p&gt;

&lt;p&gt;When you finally do an update, there's a whole ritual: find the new AMI ID, read through the release notes, assess any CVEs, draft a PR, wait for approvals, then carefully roll out nodes without taking down your workloads.&lt;/p&gt;

&lt;p&gt;It's not rocket science — it's just slow, manual, and one of those tasks that always feels lower priority than the thing currently on fire.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if the whole thing ran itself?&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The solution in one sentence
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Twice a day, a Lambda checks for new EKS AMIs. If one exists, Bedrock analyzes the risk and opens a GitHub PR. A human reviews it. Merging the PR triggers ArgoCD + Karpenter to roll out the new nodes with zero downtime.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The magic is that &lt;strong&gt;the only thing a human needs to do is read the AI's analysis and merge (or close) the PR.&lt;/strong&gt; Everything else — detection, analysis, branch creation, notification, node rollout — is automated.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture: Three clean phases
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8qckdrg596fjpqprtncw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8qckdrg596fjpqprtncw.png" alt="Three-phase EKS AMI automation pipeline: Detect via EventBridge and Lambda, AI Analyze via Amazon Bedrock and GitHub PR, Deploy via ArgoCD and Karpenter"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 1 — Detection
&lt;/h3&gt;

&lt;p&gt;An &lt;strong&gt;EventBridge scheduled rule&lt;/strong&gt; fires at 9 AM and 9 PM UTC every day. It triggers a Lambda that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Queries &lt;strong&gt;AWS SSM Parameter Store&lt;/strong&gt; for the latest EKS-optimized AMI ID (&lt;code&gt;/aws/service/eks/optimized-ami/1.34/amazon-linux-2023/recommended/image_id&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Compares it against what's currently committed in your &lt;strong&gt;GitHub repository&lt;/strong&gt; (your source of truth)&lt;/li&gt;
&lt;li&gt;If they differ — new AMI exists → triggers the Step Functions workflow&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No new AMI? The Lambda exits quietly. Nothing else happens.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 2 — AI Analysis + Pull Request
&lt;/h3&gt;

&lt;p&gt;This is where it gets interesting. &lt;strong&gt;AWS Step Functions&lt;/strong&gt; orchestrates three Lambda functions in sequence:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lambda 1 — &lt;code&gt;bedrock-analyzer&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Fetches the real AMI release notes from GitHub (awslabs/amazon-eks-ami) and sends them to &lt;strong&gt;Amazon Bedrock running Claude 3.5 Haiku&lt;/strong&gt; with this prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Analyze this Amazon EKS AMI update using the actual release notes.
New AMI ID: {ami_id}
Previous AMI ID: {previous_ami}

ACTUAL EKS AMI RELEASE NOTES:
{release_notes}

Respond in JSON with:
- risk_score: 1–10
- recommendation: APPROVE or REJECT
- summary: one-line summary of actual changes
- pr_description: full markdown PR body with CVEs, package versions,
  risk assessment, and review guidance
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output is a structured JSON object with a risk score and a ready-to-paste PR description.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lambda 2 — &lt;code&gt;gitops-updater&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Uses GitHub App credentials (stored in &lt;strong&gt;AWS Secrets Manager&lt;/strong&gt;) to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a new branch&lt;/li&gt;
&lt;li&gt;Update the &lt;strong&gt;Karpenter EC2NodeClass&lt;/strong&gt; YAML with the new AMI ID&lt;/li&gt;
&lt;li&gt;Open a Pull Request with the full Bedrock analysis embedded in the description&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Lambda 3 — &lt;code&gt;send-notification&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Fires an &lt;strong&gt;SNS email&lt;/strong&gt; to the team: "New AMI detected, PR #N is open for your review." Includes the PR link and the one-line AI summary.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The human's job:&lt;/strong&gt; Read the AI analysis. Check the YAML diff (it's literally one line — the AMI ID). Merge to approve, close to reject.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 3 — GitOps Deployment
&lt;/h3&gt;

&lt;p&gt;After the PR is merged:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ArgoCD&lt;/strong&gt; detects the commit on &lt;code&gt;main&lt;/code&gt;, auto-syncs the updated &lt;code&gt;EC2NodeClass&lt;/code&gt; manifest to the EKS cluster&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Karpenter&lt;/strong&gt; sees the new AMI ID in the &lt;code&gt;EC2NodeClass&lt;/code&gt;, provisions new EC2 nodes with the updated AMI, then gracefully drains the old nodes&lt;/li&gt;
&lt;li&gt;Workloads migrate to new nodes. &lt;strong&gt;Zero downtime.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The whole rollout happens without anyone touching &lt;code&gt;kubectl&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the PR actually looks like
&lt;/h2&gt;

&lt;p&gt;This is what your team sees in GitHub:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## EKS AMI Update — ami-04b406d4e6eaca578&lt;/span&gt;

&lt;span class="gs"&gt;**AI Risk Score: 2/10 — APPROVE**&lt;/span&gt;

&lt;span class="gu"&gt;### What changed&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Go updated to 1.25.9
&lt;span class="p"&gt;-&lt;/span&gt; Kernel updated to 6.12.79-101.147.amzn2023
&lt;span class="p"&gt;-&lt;/span&gt; No new CVEs introduced

&lt;span class="gu"&gt;### CVE Assessment&lt;/span&gt;
No critical or high-severity CVEs in this update. Two previously
known CVEs (CVE-2024-XXXX, CVE-2024-YYYY) are patched.

&lt;span class="gu"&gt;### Review guidance&lt;/span&gt;
This is a routine kernel + runtime update. Low risk. Recommend
merging during business hours with normal monitoring in place.
&lt;span class="p"&gt;
---&lt;/span&gt;
&lt;span class="ge"&gt;*Merge this PR to trigger ArgoCD + Karpenter rollout.*&lt;/span&gt;
&lt;span class="ge"&gt;*Close this PR to skip this AMI version.*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your reviewer doesn't need to dig through release notes. The AI already did it.&lt;/p&gt;




&lt;h2&gt;
  
  
  CloudFormation: everything in one stack
&lt;/h2&gt;

&lt;p&gt;The whole solution deploys from a single CloudFormation template. Here's what it provisions:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Resource&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AWS Secrets Manager&lt;/td&gt;
&lt;td&gt;GitHub App credentials&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Amazon SNS + subscription&lt;/td&gt;
&lt;td&gt;Email alerts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5 IAM roles&lt;/td&gt;
&lt;td&gt;Per-function least-privilege&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4 Lambda functions&lt;/td&gt;
&lt;td&gt;Detector, analyzer, PR creator, notifier&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Amazon Bedrock Guardrail&lt;/td&gt;
&lt;td&gt;Content filtering on AI output&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Step Functions state machine&lt;/td&gt;
&lt;td&gt;Orchestrates analyze → PR → notify&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EventBridge rule&lt;/td&gt;
&lt;td&gt;Twice-daily schedule&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Deploy it:&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; eks-ami-update &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--template-body&lt;/span&gt; file://cloudformation-template.yaml &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;--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;NotificationEmail,ParameterValue&lt;span class="o"&gt;=&lt;/span&gt;your@email.com &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;GitHubAppId,ParameterValue&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;app-id&amp;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;GitHubAppInstallationId,ParameterValue&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;install-id&amp;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;GitHubAppPrivateKey,ParameterValue&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; app.pem | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'\n'&lt;/span&gt;&lt;span class="si"&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;GitHubRepoOwner,ParameterValue&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;your-org&amp;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;GitHubRepoName,ParameterValue&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;your-repo&amp;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;GitHubFilePath,ParameterValue&lt;span class="o"&gt;=&lt;/span&gt;karpenter-configs/clusters/your-cluster/nodeclass.yaml &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;GitHubBranch,ParameterValue&lt;span class="o"&gt;=&lt;/span&gt;main &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;EKSVersion,ParameterValue&lt;span class="o"&gt;=&lt;/span&gt;1.34
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Takes about 2–3 minutes. Confirm the SNS subscription email when it arrives.&lt;/p&gt;




&lt;h2&gt;
  
  
  Prerequisites checklist
&lt;/h2&gt;

&lt;p&gt;Before deploying, you need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] An existing EKS cluster (v1.34+)&lt;/li&gt;
&lt;li&gt;[ ] Karpenter installed and configured&lt;/li&gt;
&lt;li&gt;[ ] ArgoCD installed with &lt;strong&gt;auto-sync enabled&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;[ ] A GitHub repository for Karpenter configs&lt;/li&gt;
&lt;li&gt;[ ] A GitHub App installed on that repo (you need App ID, Installation ID, and Private Key)&lt;/li&gt;
&lt;li&gt;[ ] Amazon Bedrock enabled in your region (enable Claude 3.5 Haiku access in the Bedrock console)&lt;/li&gt;
&lt;li&gt;[ ] AWS CLI + kubectl configured&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; Fork the &lt;a href="https://github.com/suryansh639/sample-eks-ami-gitops-pipeline.git" rel="noopener noreferrer"&gt;aws-samples repository&lt;/a&gt; to your own account — you need write access to configure the GitHub App. Deploy your EC2NodeClass config to the repo before running the stack.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Testing it without waiting for an AMI release
&lt;/h2&gt;

&lt;p&gt;Don't want to wait up to 12 hours for the schedule to fire? Trigger it manually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws lambda invoke &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--function-name&lt;/span&gt; eks-ami-detector &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--payload&lt;/span&gt; &lt;span class="s1"&gt;'{}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cli-binary-format&lt;/span&gt; raw-in-base64-out &lt;span class="se"&gt;\&lt;/span&gt;
  /tmp/response.json &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cat&lt;/span&gt; /tmp/response.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check your inbox. You should get an SNS email with the risk analysis and PR link within a couple of minutes.&lt;/p&gt;

&lt;p&gt;After merging, verify the ArgoCD sync:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Update your kubeconfig&lt;/span&gt;
aws eks update-kubeconfig &lt;span class="nt"&gt;--region&lt;/span&gt; &amp;lt;region&amp;gt; &lt;span class="nt"&gt;--name&lt;/span&gt; &amp;lt;cluster-name&amp;gt;

&lt;span class="c"&gt;# Check ArgoCD sync policy&lt;/span&gt;
kubectl get application karpenter-nodeclass &lt;span class="nt"&gt;-n&lt;/span&gt; argocd &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{.spec.syncPolicy}'&lt;/span&gt;

&lt;span class="c"&gt;# Verify the AMI ID was applied&lt;/span&gt;
kubectl get ec2nodeclass default &lt;span class="nt"&gt;-o&lt;/span&gt; yaml | &lt;span class="nb"&gt;grep &lt;/span&gt;ami-
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Common issues and how to fix them
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;SNS subscription not confirmed&lt;/strong&gt; — Check your spam folder. The confirmation email comes from AWS and sometimes gets filtered.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub App auth failure&lt;/strong&gt; — Double-check the App is installed on the correct repository with read/write permissions. Regenerate the private key in GitHub if needed and re-run the CloudFormation update.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bedrock access denied&lt;/strong&gt; — Go to the Amazon Bedrock console → Model access → enable Claude 3.5 Haiku in your region. This is a manual step that's easy to miss.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ArgoCD not syncing&lt;/strong&gt; — Verify the Application resource has &lt;code&gt;spec.syncPolicy.automated&lt;/code&gt; set. Check that the repo URL and path match exactly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step Functions failures&lt;/strong&gt; — Check CloudWatch Logs for the failing Lambda. 99% of the time it's an IAM permission issue or a missing secret.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why this architecture is worth copying
&lt;/h2&gt;

&lt;p&gt;A few design decisions I want to highlight:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub PRs as the approval interface&lt;/strong&gt; — Engineers already live in GitHub. Using a PR as the human gate means no new tool to learn, built-in commenting, and a permanent audit trail in Git history. The PR description IS the change record.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI analysis on real release notes&lt;/strong&gt; — The Bedrock prompt fetches actual release notes from the awslabs/amazon-eks-ami repo. It's not making things up — it's summarizing real content. The risk score is grounded in actual CVE and package data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Karpenter over managed node groups&lt;/strong&gt; — Karpenter watches the EC2NodeClass for changes and handles the node lifecycle automatically. You don't need to write any drain/cordon scripts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Least-privilege IAM&lt;/strong&gt; — Each Lambda has its own role with only the permissions it needs. The CF template provisions five separate roles. This matters in production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Guardrails on Bedrock&lt;/strong&gt; — The solution includes a Bedrock Guardrail for content filtering on the AI output. Belt and suspenders.&lt;/p&gt;




&lt;h2&gt;
  
  
  Cleaning up
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws cloudformation delete-stack &lt;span class="nt"&gt;--stack-name&lt;/span&gt; eks-ami-update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  What I'd add next
&lt;/h2&gt;

&lt;p&gt;A few things that would make this even better:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Slack notification&lt;/strong&gt; instead of (or in addition to) SNS email — PR link directly in your #platform channel&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dry-run mode&lt;/strong&gt; — run the full pipeline but don't actually open a PR, just log the analysis&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-cluster support&lt;/strong&gt; — one stack managing AMI updates across dev/staging/prod with different approval thresholds per environment&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom risk criteria&lt;/strong&gt; — tune the Bedrock prompt to your org's specific compliance requirements (PCI-DSS, SOC 2, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatic REJECT on critical CVEs&lt;/strong&gt; — skip the PR entirely and alert the team if the risk score is 8+&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Get the code
&lt;/h2&gt;

&lt;p&gt;Fork the repo, follow the README, and deploy:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/suryansh639/sample-eks-ami-gitops-pipeline.git" rel="noopener noreferrer"&gt;GitHub: suryansh639/sample-eks-ami-gitops-pipeline&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The CloudFormation template, Lambda code, and example Karpenter configs are all there.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;The goal wasn't to remove humans from the loop — it was to remove the &lt;em&gt;boring&lt;/em&gt; part of the loop. The AI reads the release notes. The AI writes the PR description. The human decides. The automation executes.&lt;/p&gt;

&lt;p&gt;That's the right split. And it means your nodes actually get updated on time, every time, with a full audit trail and no 2 AM surprises.&lt;/p&gt;

&lt;p&gt;If you try this out, drop a comment — I'd love to hear what customizations you make.&lt;/p&gt;




</description>
      <category>aws</category>
      <category>kubernetes</category>
      <category>devops</category>
      <category>containers</category>
    </item>
    <item>
      <title>What If You Need Two ArgoCD Instances in One EKS Cluster?</title>
      <dc:creator>POTHURAJU JAYAKRISHNA YADAV</dc:creator>
      <pubDate>Sat, 30 May 2026 08:26:58 +0000</pubDate>
      <link>https://dev.to/aws-builders/what-if-you-need-two-argocd-instances-in-one-eks-cluster-57id</link>
      <guid>https://dev.to/aws-builders/what-if-you-need-two-argocd-instances-in-one-eks-cluster-57id</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4h3333pda5omttfimuou.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4h3333pda5omttfimuou.png" alt="Cover Image" width="800" height="440"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Most ArgoCD tutorials start the same way:&lt;/p&gt;

&lt;p&gt;Deploy ArgoCD.&lt;/p&gt;

&lt;p&gt;Connect a Git repository.&lt;/p&gt;

&lt;p&gt;Create an Application.&lt;/p&gt;

&lt;p&gt;Done.&lt;/p&gt;

&lt;p&gt;But what happens when multiple teams start sharing the same Kubernetes cluster?&lt;/p&gt;

&lt;p&gt;Recently I was exploring a scenario where a single Amazon EKS cluster needed to support two different ArgoCD environments:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Custom ArgoCD instance for the Platform Team&lt;/li&gt;
&lt;li&gt;A Managed ArgoCD instance for Application Teams&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Custom ArgoCD would manage infrastructure components such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;cert-manager&lt;/li&gt;
&lt;li&gt;external-dns&lt;/li&gt;
&lt;li&gt;monitoring&lt;/li&gt;
&lt;li&gt;ingress controllers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While the Managed ArgoCD would be used by application teams to deploy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;APIs&lt;/li&gt;
&lt;li&gt;frontends&lt;/li&gt;
&lt;li&gt;microservices&lt;/li&gt;
&lt;li&gt;business applications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The challenge was figuring out how to keep both environments isolated while still sharing the same EKS cluster, Cognito User Pool, and AWS Application Load Balancer.&lt;/p&gt;

&lt;p&gt;At first, I thought:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why not just use a single ArgoCD instance?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The more I thought about it, the more questions came up.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How do we separate responsibilities?&lt;/li&gt;
&lt;li&gt;How do we avoid accidental changes?&lt;/li&gt;
&lt;li&gt;How do we provide SSO?&lt;/li&gt;
&lt;li&gt;How do we keep costs under control?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That led me down an interesting path:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can we run two ArgoCD instances inside the same EKS cluster, authenticate both using Cognito SSO, and still share the same ALB?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Turns out we can.&lt;/p&gt;

&lt;p&gt;This article walks through the architecture, challenges, lessons learned, and a few surprises along the way.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Scenario
&lt;/h1&gt;

&lt;p&gt;Imagine you have a Kubernetes cluster used by multiple teams.&lt;/p&gt;

&lt;p&gt;The Platform Team owns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;cert-manager&lt;/li&gt;
&lt;li&gt;external-dns&lt;/li&gt;
&lt;li&gt;monitoring&lt;/li&gt;
&lt;li&gt;ingress controllers&lt;/li&gt;
&lt;li&gt;cluster add-ons&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Application Teams own:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;APIs&lt;/li&gt;
&lt;li&gt;frontends&lt;/li&gt;
&lt;li&gt;microservices&lt;/li&gt;
&lt;li&gt;business applications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A single ArgoCD instance technically works.&lt;/p&gt;

&lt;p&gt;But should both groups operate from the same GitOps control plane?&lt;/p&gt;

&lt;p&gt;For smaller environments, probably yes.&lt;/p&gt;

&lt;p&gt;For larger environments, things start getting messy.&lt;/p&gt;

&lt;p&gt;Different permissions.&lt;/p&gt;

&lt;p&gt;Different ownership boundaries.&lt;/p&gt;

&lt;p&gt;Different operational responsibilities.&lt;/p&gt;

&lt;p&gt;That was the motivation behind splitting ArgoCD into two instances.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Goal
&lt;/h1&gt;

&lt;p&gt;The architecture I wanted looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EKS Cluster

├── ArgoCD (Platform Team) - Custom argocd
│     ├── cert-manager
│     ├── external-dns
│     └── monitoring
│
└── ArgoCD (Application Teams) - Managed argocd
      ├── frontend
      ├── backend
      └── business applications
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And on top of that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Single Sign-On&lt;/li&gt;
&lt;li&gt;Fully automated provisioning&lt;/li&gt;
&lt;li&gt;Infrastructure as Code&lt;/li&gt;
&lt;li&gt;Minimal AWS cost&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  The Final Architecture
&lt;/h1&gt;

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

&lt;p&gt;The final design consisted of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Developer
    |
    | HTTPS
    v

Shared AWS ALB
(group.name = argocd)

    |
    +---------------------+
    |                     |
    v                     v

argocd              argocd2
Namespace:          Namespace:
argocd              argocd-managed

Platform Team       Application Teams

    |                     |
    +----------+----------+
               |
               v

AWS Cognito User Pool
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both ArgoCD instances run inside the same EKS cluster.&lt;/p&gt;

&lt;p&gt;The difference is that each lives in its own namespace.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;This gives logical separation without requiring another cluster.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Cognito?
&lt;/h1&gt;

&lt;p&gt;Before choosing Cognito, I looked at AWS IAM Identity Center.&lt;/p&gt;

&lt;p&gt;Identity Center is generally the preferred enterprise solution.&lt;/p&gt;

&lt;p&gt;However, for this learning exercise I wanted everything automated using Terraform.&lt;/p&gt;

&lt;p&gt;My ideal deployment process was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and automatically create:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User Pool&lt;/li&gt;
&lt;li&gt;App Clients&lt;/li&gt;
&lt;li&gt;Groups&lt;/li&gt;
&lt;li&gt;Users&lt;/li&gt;
&lt;li&gt;Passwords&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;without opening the AWS Console.&lt;/p&gt;

&lt;p&gt;Cognito made that surprisingly easy.&lt;/p&gt;




&lt;h1&gt;
  
  
  Building the Authentication Layer
&lt;/h1&gt;

&lt;p&gt;The first step was creating a Cognito User Pool.&lt;/p&gt;

&lt;p&gt;Each ArgoCD instance gets its own App Client.&lt;/p&gt;

&lt;p&gt;Visually, it looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cognito User Pool

├── App Client
│     └── ArgoCD Platform
│
└── App Client
      └── ArgoCD Managed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's what it looked like in AWS.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqqbeo900l4bzt9w763kq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqqbeo900l4bzt9w763kq.png" alt="Cognito User Pool showing app clients and ArgoCDAdmins group" width="800" height="301"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One thing I particularly liked was eliminating manual user creation.&lt;/p&gt;

&lt;p&gt;Terraform creates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User Pool&lt;/li&gt;
&lt;li&gt;Domain&lt;/li&gt;
&lt;li&gt;App Clients&lt;/li&gt;
&lt;li&gt;Groups&lt;/li&gt;
&lt;li&gt;Admin User&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything becomes Infrastructure as Code.&lt;/p&gt;

&lt;p&gt;No temporary passwords.&lt;/p&gt;

&lt;p&gt;No manual console work.&lt;/p&gt;

&lt;p&gt;No forgotten setup steps.&lt;/p&gt;




&lt;h1&gt;
  
  
  Connecting ArgoCD to Cognito
&lt;/h1&gt;

&lt;p&gt;ArgoCD supports OIDC natively.&lt;/p&gt;

&lt;p&gt;That means Cognito can act as the identity provider.&lt;/p&gt;

&lt;p&gt;The authentication flow becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User
  |
  v
ArgoCD
  |
  v
Cognito Hosted UI
  |
  v
Authentication
  |
  v
OIDC Callback
  |
  v
ArgoCD Dashboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once logged in, ArgoCD reads the Cognito groups from the token and maps them to ArgoCD roles.&lt;/p&gt;

&lt;p&gt;That means administrators are controlled centrally through Cognito instead of managing users inside ArgoCD itself.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Shared ALB Experiment
&lt;/h1&gt;

&lt;p&gt;This was probably the most interesting part.&lt;/p&gt;

&lt;p&gt;Initially I considered creating two separate Application Load Balancers.&lt;/p&gt;

&lt;p&gt;One for each ArgoCD instance.&lt;/p&gt;

&lt;p&gt;That would work.&lt;/p&gt;

&lt;p&gt;But it also means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More AWS resources&lt;/li&gt;
&lt;li&gt;More management&lt;/li&gt;
&lt;li&gt;More cost&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then I remembered the AWS Load Balancer Controller supports Ingress Groups.&lt;/p&gt;

&lt;p&gt;That changed everything.&lt;/p&gt;

&lt;p&gt;Instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ALB #1 → ArgoCD
ALB #2 → ArgoCD Managed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I could use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;One ALB
Two Hostnames
Two ArgoCD Instances
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ALB creates listener rules based on the hostname.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1g8zzrafbecsgb9fqxf7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1g8zzrafbecsgb9fqxf7.png" alt="ALB listener rules showing host-based routing for both ArgoCD instances" width="800" height="326"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And from Kubernetes:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foai143ied4666bb68i13.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foai143ied4666bb68i13.png" alt="kubectl get ingress showing both ingresses sharing the same ALB address" width="798" height="238"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Both ingress resources point to the same ALB.&lt;/p&gt;

&lt;p&gt;The routing happens automatically based on the Host header.&lt;/p&gt;

&lt;p&gt;A nice cost optimization with very little additional complexity.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Problem Nobody Warned Me About
&lt;/h1&gt;

&lt;p&gt;The first ArgoCD deployment worked perfectly.&lt;/p&gt;

&lt;p&gt;The second one failed immediately.&lt;/p&gt;

&lt;p&gt;The error looked something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;customresourcedefinitions.apiextensions.k8s.io already exists
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At first it wasn't obvious.&lt;/p&gt;

&lt;p&gt;Then it clicked.&lt;/p&gt;

&lt;p&gt;ArgoCD installs CRDs.&lt;/p&gt;

&lt;p&gt;CRDs are cluster-scoped resources.&lt;/p&gt;

&lt;p&gt;The first installation already created them.&lt;/p&gt;

&lt;p&gt;The second installation attempted to create them again.&lt;/p&gt;

&lt;p&gt;Kubernetes refused.&lt;/p&gt;

&lt;p&gt;The fix was simple:&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;crds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;install&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once I disabled CRD installation for the second ArgoCD instance, everything worked.&lt;/p&gt;

&lt;p&gt;It was one of those issues that takes a while to understand but only seconds to fix.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Login Experience
&lt;/h1&gt;

&lt;p&gt;After deployment, the user experience was exactly what I wanted.&lt;/p&gt;

&lt;p&gt;First, users see the ArgoCD login screen.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fywksma4y8v0x1ph8y09f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fywksma4y8v0x1ph8y09f.png" alt="ArgoCD login page with Log in via Cognito button" width="799" height="342"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Selecting the Cognito login option redirects users to the Hosted 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%2Fet7kq8fyy1gucjogl3t8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fet7kq8fyy1gucjogl3t8.png" alt="Cognito Hosted UI login form after redirect" width="800" height="391"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After successful authentication, users land directly inside ArgoCD.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6300yryza29u6ahdggwr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6300yryza29u6ahdggwr.png" alt="ArgoCD dashboard after successful SSO login showing admin user" width="800" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcydnko4ef6iqovo0fj1r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcydnko4ef6iqovo0fj1r.png" alt="ArgoCD dashboard after successful SSO login showing admin user" width="800" height="369"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;No local accounts.&lt;/p&gt;

&lt;p&gt;No shared admin credentials.&lt;/p&gt;

&lt;p&gt;No Kubernetes secret lookups.&lt;/p&gt;

&lt;p&gt;Just SSO.&lt;/p&gt;




&lt;h1&gt;
  
  
  Another Small Gotcha
&lt;/h1&gt;

&lt;p&gt;While testing Applications, I ran into this error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app path is not a directory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I had accidentally pointed ArgoCD to a YAML file instead of a directory.&lt;/p&gt;

&lt;p&gt;ArgoCD expects:&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;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;manifests/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;not:&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;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;manifests/app.yaml&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A small detail, but one that can waste a surprising amount of time when you're learning.&lt;/p&gt;




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

&lt;p&gt;The most valuable lesson wasn't about Cognito.&lt;/p&gt;

&lt;p&gt;It wasn't about Terraform.&lt;/p&gt;

&lt;p&gt;It wasn't even about ArgoCD.&lt;/p&gt;

&lt;p&gt;It was about ownership.&lt;/p&gt;

&lt;p&gt;As environments grow, separating responsibilities becomes more important than simply making things work.&lt;/p&gt;

&lt;p&gt;Running a Custom ArgoCD for platform operations and a Managed ArgoCD for application teams gave:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Better operational boundaries&lt;/li&gt;
&lt;li&gt;Cleaner RBAC&lt;/li&gt;
&lt;li&gt;Clear ownership between teams&lt;/li&gt;
&lt;li&gt;Reduced risk of accidental changes&lt;/li&gt;
&lt;li&gt;Independent GitOps workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The platform team can safely manage infrastructure applications while application teams operate in their own ArgoCD environment without interfering with cluster-level services.&lt;/p&gt;

&lt;p&gt;And because both instances share a single ALB, the cost impact remained minimal.&lt;/p&gt;




&lt;h1&gt;
  
  
  Would I Do This Again?
&lt;/h1&gt;

&lt;p&gt;For a small team?&lt;/p&gt;

&lt;p&gt;Probably not.&lt;/p&gt;

&lt;p&gt;A single ArgoCD instance is easier to manage.&lt;/p&gt;

&lt;p&gt;For organizations with dedicated platform teams and application teams?&lt;/p&gt;

&lt;p&gt;Absolutely.&lt;/p&gt;

&lt;p&gt;The pattern scales nicely, provides cleaner ownership boundaries, and integrates well with SSO.&lt;/p&gt;

&lt;p&gt;Most importantly, the entire environment can be created with a single:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which is exactly what I wanted from the beginning.&lt;/p&gt;




&lt;p&gt;If you've experimented with multiple ArgoCD instances or different GitOps multi-tenancy approaches, I'd love to hear how you've solved it.&lt;/p&gt;

&lt;p&gt;Happy GitOps 🚀&lt;/p&gt;




&lt;h2&gt;
  
  
  GitHub Repository
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/jayakrishnayadav24/eks-argocd-cognito-sso" rel="noopener noreferrer"&gt;https://github.com/jayakrishnayadav24/eks-argocd-cognito-sso&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>kubernetes</category>
      <category>argocd</category>
      <category>terraform</category>
    </item>
    <item>
      <title>"Reinstalling Won't Fix It": A Cross-App Shared-Auth Deadlock After Switching Phones</title>
      <dc:creator>Kento IKEDA</dc:creator>
      <pubDate>Sat, 30 May 2026 07:26:30 +0000</pubDate>
      <link>https://dev.to/aws-builders/reinstalling-wont-fix-it-a-cross-app-shared-auth-deadlock-after-switching-phones-40do</link>
      <guid>https://dev.to/aws-builders/reinstalling-wont-fix-it-a-cross-app-shared-auth-deadlock-after-switching-phones-40do</guid>
      <description>&lt;p&gt;After migrating to a new Android phone, a few specific apps stopped launching. Amazon Shopping and Kindle would freeze on a blank white or black screen for a while, then close on their own. Reinstalling, clearing storage, updating the OS — none of it helped. Going through the usual support steps changed nothing.&lt;/p&gt;

&lt;p&gt;What finally fixed it was clearing the storage of &lt;em&gt;every&lt;/em&gt; Amazon app at once. Tracing the cause through ADB logs, it turned out that authentication data shared across multiple apps had become inconsistent, and the auth-retrieval step at startup was deadlocking.&lt;/p&gt;

&lt;p&gt;The incident itself happened with a specific Pixel-and-Amazon combination, but structurally it's a pattern that can hit any app where "authentication data shared across apps" meets "many subsystems initialized in parallel at startup for speed." It's worth knowing about whether you design SDKs, build apps, or handle ops and support, so I'm leaving it here as a case study.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: This happened on my own device, with my own account. I'm reading the diagnostic output that the OS itself wrote out — not decompiling any app.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What happened after switching phones
&lt;/h2&gt;

&lt;p&gt;Right after migrating data to a Pixel 10a, only certain apps refused to launch.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Affected: Amazon Shopping, Kindle&lt;/li&gt;
&lt;li&gt;Symptom: open the app, it freezes on a blank white or black screen for about ten seconds, then closes on its own&lt;/li&gt;
&lt;li&gt;Everything else: other apps work fine&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The officially suggested remedies are generic — "reinstall the app," "clear the cache," "update the OS" — and none of them worked. When the root cause is in the OS or the data migration rather than the app itself, the standard support path has a hard time isolating it.&lt;/p&gt;

&lt;p&gt;At first I couldn't even tell whether it was an OS problem, an app problem, or an Amazon account problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  When nothing works, change the framing
&lt;/h2&gt;

&lt;p&gt;I tried all the standard fixes.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reinstalling the affected apps&lt;/li&gt;
&lt;li&gt;Clearing storage of just the affected apps&lt;/li&gt;
&lt;li&gt;Clearing the cache of the affected apps&lt;/li&gt;
&lt;li&gt;Updating every app in the Play Store&lt;/li&gt;
&lt;li&gt;Updating the OS&lt;/li&gt;
&lt;li&gt;Restarting the device&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The affected apps still wouldn't launch, no matter what.&lt;/p&gt;

&lt;p&gt;The key realization: as long as you think of it as "a problem with one app," you won't fix it. Reinstalling just the affected app, or clearing just that app's storage, changes nothing. If that's the case, the cause probably isn't contained within a single app.&lt;/p&gt;

&lt;p&gt;Only once you reframe it as "a problem across a group of apps" does the solution come into view.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hypothesis: the shared authentication data is corrupted
&lt;/h2&gt;

&lt;p&gt;Let me start with a hypothesis built only from public information. I'll back it up with logs in the second half.&lt;/p&gt;

&lt;p&gt;Amazon's Login with Amazon SDK has a mechanism that lets other apps reuse the Amazon Shopping app's logged-in state. This is documented officially.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.amazon.com/docs/login-with-amazon/customer-experience-android.html" rel="noopener noreferrer"&gt;https://developer.amazon.com/docs/login-with-amazon/customer-experience-android.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;According to the docs, if the user is already signed in to the Amazon Shopping app, an app that integrates Login with Amazon won't ask them to re-enter account details — the SDK recognizes and reuses the auth state of the Amazon Shopping app or the Fire OS device. That's single sign-on (SSO). The SDK's internal package name is &lt;code&gt;com.amazon.identity.auth.map.device&lt;/code&gt;, which also appears in Amazon's official migration guide.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.amazon.com/docs/login-with-amazon/upgrade-android-sdk.html" rel="noopener noreferrer"&gt;https://developer.amazon.com/docs/login-with-amazon/upgrade-android-sdk.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What we can say from this is that Amazon's authentication layer (referred to internally in the SDK as MAP) is designed so that other apps can reference the Amazon Shopping app's logged-in state. What the docs directly describe is SSO for apps using Login with Amazon, but it's reasonable to think that Amazon's own apps such as Kindle and Prime Video share the same auth layer too.&lt;/p&gt;

&lt;p&gt;The hypothesis, then: during data migration, only part of the authentication data was carried over in a corrupted state, and the startup process that tries to fetch that shared data is getting stuck. If that's right, it explains why nothing short of wiping the apps that hold the shared data will fix it.&lt;/p&gt;

&lt;p&gt;That said, specifics like "the first-installed app holds the auth data as the representative" or "only one particular app is the source" can't be asserted from public information. I'll check how solid the hypothesis is by reading the ANR trace in the second half.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix: clear storage for the whole group of related apps at once
&lt;/h2&gt;

&lt;p&gt;Here's the fix up front. The concrete steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;code&gt;Settings &amp;gt; Apps &amp;gt; See all XX apps&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;List every Amazon app installed&lt;/li&gt;
&lt;li&gt;For each one, run &lt;code&gt;Storage &amp;amp; cache &amp;gt; Clear storage&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Once they're all cleared, restart the device&lt;/li&gt;
&lt;li&gt;Then open Amazon Shopping or Kindle — if a login screen appears, you're good&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Examples of apps to target:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Amazon Shopping&lt;/li&gt;
&lt;li&gt;Kindle&lt;/li&gt;
&lt;li&gt;Amazon Prime Video&lt;/li&gt;
&lt;li&gt;Amazon Music&lt;/li&gt;
&lt;li&gt;Amazon Photos&lt;/li&gt;
&lt;li&gt;Amazon Alexa&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important point is that what you need to wipe is not "the app that's failing" but "every app that shares the authentication data." In my case, the one that finally did it was clearing Prime Video's storage. It was an app I barely ever opened, and until I cleared it, clearing the other Amazon apps did nothing. It may well have been the source of the shared data.&lt;/p&gt;

&lt;p&gt;Migration tools restore apps automatically from the old device's app list. As a result, you can end up with Amazon apps you haven't used in ages — ones you've forgotten you ever installed. In the user's mind it's an "app I don't use," but in the authentication-sharing network it's a full-fledged node, and the corrupted data sitting there drags down the apps you are launching. The instinct to "only clear the app that's failing" or "only clear the apps I actually use" backfires here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verifying the hypothesis with the ANR trace
&lt;/h2&gt;

&lt;p&gt;Now the main part. Let's verify whether this really is a shared-auth deadlock, using the ANR (Application Not Responding) stack trace.&lt;/p&gt;

&lt;p&gt;The first thing to establish: what's killing the app is an "ANR," not a "crash." A crash throws an exception and the process drops immediately; an ANR is the system force-closing the app after the main thread has failed to respond for some time (roughly several seconds to ten). Freezing on a blank screen and then closing is the classic ANR symptom — not an exception, but a timeout while waiting for a response.&lt;/p&gt;

&lt;p&gt;Since this was happening on my own device with my own account, I connected the Pixel to a Mac over ADB and pulled the diagnostic log (the stack trace) that the OS wrote out when the ANR occurred. Again, I'm not decompiling the app — just reading the diagnostic output the OS left behind.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;adb shell dumpsys dropbox &lt;span class="nt"&gt;--print&lt;/span&gt; data_app_anr | &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-A&lt;/span&gt; 200 &lt;span class="s2"&gt;"Process: com.amazon.mShop.android.shopping"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The "DropBox" in &lt;code&gt;dumpsys dropbox&lt;/code&gt; refers to &lt;code&gt;DropBoxManager&lt;/code&gt;, the Android system-log mechanism that stores diagnostic entries (crashes, ANRs, and so on) over time. It has nothing to do with the cloud storage service of the same name. &lt;code&gt;--print data_app_anr&lt;/code&gt; pulls only the entries tagged as app ANRs, filtered here by Amazon Shopping's process name.&lt;/p&gt;

&lt;p&gt;The trace recorded several threads running in parallel at startup. The key part: they were waiting on each other's locks. Let's read them in order.&lt;/p&gt;

&lt;h3&gt;
  
  
  main thread (tid=1): the UI itself, stuck
&lt;/h3&gt;

&lt;p&gt;The main thread was stuck while running a startup task called &lt;code&gt;AndroidComponentDetectTask&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"main" prio=5 tid=1 Blocked
  at java.util.concurrent.ConcurrentHashMap.computeIfAbsent(...)
  - waiting to lock &amp;lt;0x00eb4d79&amp;gt; held by thread 37
  at com.amazon.platform.service.ServiceRegistryImpl.getService(...)
  at com.amazon.mShop.appStart.AndroidComponentDetectTask.apply(...)
  ...
  at android.app.ActivityThread.handleBindApplication(...)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's trying to acquire a lock, &lt;code&gt;&amp;lt;0x00eb4d79&amp;gt;&lt;/code&gt;, and waiting for thread 37 to release it. This lock is one the Service Registry (the common registry where each subsystem registers and retrieves itself) takes internally when fetching or creating a service. On Android the main thread &lt;em&gt;is&lt;/em&gt; the UI thread, so when it stops here, nothing gets drawn and the screen stays blank.&lt;/p&gt;

&lt;h3&gt;
  
  
  thread 36: collateral damage waiting on the same lock
&lt;/h3&gt;

&lt;p&gt;The error-reporting init task (thread 36) was waiting on the exact same lock as main.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"StagedExecutor2-pool-19-thread-1" prio=5 tid=36 Blocked
  at java.util.concurrent.ConcurrentHashMap.computeIfAbsent(...)
  - waiting to lock &amp;lt;0x00eb4d79&amp;gt; held by thread 37
  at com.amazon.platform.service.ServiceRegistryImpl.getService(...)
  at com.amazon.mShop.sam.log.SAMLogManager.initialize(...)
  at com.amazon.mShop.errorReporting.ErrorReporter.startSession(...)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also waiting on &lt;code&gt;&amp;lt;0x00eb4d79&amp;gt;&lt;/code&gt;. This Service Registry lock is a congestion point that multiple threads fight over at startup.&lt;/p&gt;

&lt;h3&gt;
  
  
  thread 37: the culprit, holding a lock while waiting on auth data
&lt;/h3&gt;

&lt;p&gt;The problem thread is thread 37. It was holding &lt;code&gt;&amp;lt;0x00eb4d79&amp;gt;&lt;/code&gt; (the Service Registry lock) while trying to acquire another lock, &lt;code&gt;&amp;lt;0x004a4835&amp;gt;&lt;/code&gt;, and getting stuck.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"StagedExecutor3-pool-20-thread-1" prio=5 tid=37 Blocked
  - waiting to lock &amp;lt;0x004a4835&amp;gt; held by thread 62
  at com.amazon.identity.auth.device.api.MAPAccountManager.getAccount(...)
  at com.amazon.mShop.minerva.MinervaWrapperMAPClient.fetchAndSetAccountAttributeForTeen(...)
  at com.amazon.mShop.minerva.MinervaWrapperMAPClient.&amp;lt;init&amp;gt;(...)
  at com.amazon.mShop.minerva.MinervaWrapperServiceImpl.initializeMinervaClientIfNeeded(...)
  at com.amazon.platform.service.ServiceRegistryImpl.instantiateService(...)
  at java.util.concurrent.ConcurrentHashMap.computeIfAbsent(...)
  - locked &amp;lt;0x00eb4d79&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reading bottom to top, the sequence is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Service Registry takes its internal lock &lt;code&gt;&amp;lt;0x00eb4d79&amp;gt;&lt;/code&gt; to create a service (at this moment, main and thread 36 are made to wait)&lt;/li&gt;
&lt;li&gt;Still holding that lock, it proceeds into initializing the metrics SDK (Minerva) client&lt;/li&gt;
&lt;li&gt;Inside that, it calls &lt;code&gt;MAPAccountManager.getAccount&lt;/code&gt; to fetch the currently logged-in account&lt;/li&gt;
&lt;li&gt;The auth SDK (MAP) tries to take another lock, &lt;code&gt;&amp;lt;0x004a4835&amp;gt;&lt;/code&gt;, internally&lt;/li&gt;
&lt;li&gt;But that lock is held by thread 62 and never comes back&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Thread 37 sits in the decisive position that triggers the deadlock: holding the Service Registry lock while frozen waiting for auth data. Because it won't release the lock it holds, main and thread 36 — which are waiting on it — stall in turn.&lt;/p&gt;

&lt;h3&gt;
  
  
  thread 27: another one waiting on the auth lock
&lt;/h3&gt;

&lt;p&gt;On top of that, thread 27 (Weblab, fetching A/B-test flags) was also waiting on the same auth lock &lt;code&gt;&amp;lt;0x004a4835&amp;gt;&lt;/code&gt; as thread 37.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"StagedExecutor1-pool-15-thread-1" prio=5 tid=27 Blocked
  - waiting to lock &amp;lt;0x004a4835&amp;gt; held by thread 62
  at com.amazon.identity.auth.device.api.MultipleAccountManager.getAccountForMapping(...)
  at com.amazon.mShop.sso.SSOUtil.getCurrentAccountFromDisk(...)
  at com.amazon.mShop.core.features.weblab.WeblabServiceImpl.getTreatmentAndCacheForAppStartWithTrigger(...)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Weblab also needs auth information at startup, and via &lt;code&gt;getAccountForMapping&lt;/code&gt; it's waiting on the same auth lock to be released. Note that thread 37 reaches the lock through &lt;code&gt;MAPAccountManager&lt;/code&gt; and thread 27 through &lt;code&gt;MultipleAccountManager&lt;/code&gt; — two different APIs converging on one internal lock.&lt;/p&gt;

&lt;h3&gt;
  
  
  The big picture: auth-data retrieval is where every task converges
&lt;/h3&gt;

&lt;p&gt;Laid out, the dependencies look like this:&lt;/p&gt;

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

&lt;p&gt;What stands out is that the tasks meant to run in parallel at startup (metrics, error reporting, A/B testing, component detection) all ultimately converge on a single point: "fetching the MAP auth data." Minerva and Weblab are supposed to be independent features, yet somewhere in initialization each one reaches for the same auth SDK to find out "who is logged in right now."&lt;/p&gt;

&lt;p&gt;That auth-data retrieval never returns, because the shared data is corrupted. Every task that needs auth stalls; and because the task &lt;em&gt;holding&lt;/em&gt; the Service Registry lock has stalled, even tasks unrelated to auth (main, error reporting) get dragged down. That's the full chain that leaves the screen blank until the ANR fires.&lt;/p&gt;

&lt;p&gt;Following thread 62 — the one stuck while holding the auth lock — it was sending a query to another process via a ContentProvider and waiting for the response. A ContentProvider is Android's mechanism for sharing data between apps, and Amazon's apps appear to use it to pass authentication data around. It seems thread 62 was stuck holding the auth lock because one of the sharing sources never returned a response. Which app, and why it didn't respond, can't be pinned down from this trace alone. But the structure — "go fetch the shared auth data from the source, and it never comes back" — is consistent with the fact that wiping every Amazon app's storage fixed it.&lt;/p&gt;

&lt;p&gt;Strictly speaking, this isn't a circular wait where two threads grab each other's locks (the textbook deadlock). It's a hang: a thread holding a lock freezes waiting on an external process, and the threads waiting on it stall in a chain. But since the outcome — "stuck holding a lock, with everyone waiting on it blocked forever" — is no different from a deadlock, I'm calling it a deadlock in this article.&lt;/p&gt;

&lt;h2&gt;
  
  
  The design pitfalls this case reveals
&lt;/h2&gt;

&lt;p&gt;The textbook lessons — "acquire locks in a consistent order," "don't block the main thread" — apply here too, of course. But what the trace really surfaced is the pitfall that emerges when well-intentioned design decisions pile up. Parallelization for speed, auth lookups for functionality, cross-app data sharing for convenience. Each is reasonable on its own, but stacked together they become the following three pitfalls.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pitfall 1: parallel-init speedups backfire on shared resources
&lt;/h3&gt;

&lt;p&gt;Initializing subsystems in parallel to speed up startup looks like a correct optimization. Indeed, the trace recorded several init tasks running concurrently on separate threads — metrics, error reporting, A/B testing, component detection.&lt;/p&gt;

&lt;p&gt;The thing is, many of them internally call the same shared operations: "register with the Service Registry" and "fetch the current logged-in account." Even run in parallel, they end up serialized on the shared resource's lock. On its own that just makes startup slower — but when the thread holding a lock stalls on something else, everyone waiting gets swept up all at once, as happened here.&lt;/p&gt;

&lt;p&gt;Parallelization aimed at speed becomes effectively serial under shared-resource contention, and in the worst case deadlocks. The most dangerous spot is the assumption that "it parallelized, so it must be faster." When you add startup tasks, you have to look at how each one touches shared resources (registry, auth, settings store) as a set — otherwise you not only fail to get the scaling benefit, you raise the odds of a deadlock.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pitfall 2: auth has become an implicit dependency of every feature
&lt;/h3&gt;

&lt;p&gt;The most surprising thing in the trace was that both metrics and A/B testing — features that look unrelated to auth — were reaching for "who is logged in right now" during initialization. Metrics wants to attach user attributes; A/B testing wants to bucket by account. The reasons are each fair enough, but the result is that the auth SDK has become an implicit dependency point for the entire app.&lt;/p&gt;

&lt;p&gt;When auth-data retrieval jams at a single point, it's not auth itself that stops — it's every feature that referenced auth, stalling in a chain. You need to recognize that auth isn't "a concern around the login screen" but "a critical path of the entire startup sequence." If you count how many subsystems call auth-data retrieval at startup in your own app, the number may be higher than you'd imagine.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pitfall 3: ownership of shared data goes adrift during migration
&lt;/h3&gt;

&lt;p&gt;A design where multiple apps share authentication data is convenient for users — sign in once and you don't need to log in again on the other apps. The problem is that "who owns this shared data, and who fixes it when it breaks" is left implicit.&lt;/p&gt;

&lt;p&gt;Suppose there's an implicit rule like "the first-installed app is the representative." If migration doesn't reproduce the install order or state, the ownership relationship goes adrift. The owner sits there holding corrupted data while other apps go to reference it. The fact that nothing was fixed until I cleared Prime Video this time may have this ownership ambiguity in the background. Shared data needs a fallback — another app taking over, or safely regenerating the data — for when the owner disappears or its data breaks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons for support and for users
&lt;/h2&gt;

&lt;p&gt;Even if you're not in a position to change the design, knowing this structure changes how fast you can respond.&lt;/p&gt;

&lt;p&gt;For support: keep in mind that "please reinstall" only works when the problem is contained within a single app. For a post-migration report of "only certain apps won't launch," suspect that migration left the shared data corrupted, and being able to offer the next move — "clear storage for the related apps as a group" — changes the opening response. Even just asking "did this start right after switching phones?" up front can sometimes narrow the investigation considerably.&lt;/p&gt;

&lt;p&gt;For users: think of the cleanup target as "every app from the same provider," not "the app that's giving you trouble." Keeping in mind that even an app you don't use is a node in the sharing network, and that corruption there causes collateral damage, raises your odds of getting out of it on your own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other cases where the same pattern can occur
&lt;/h2&gt;

&lt;p&gt;It's not just Amazon — there are plenty of designs where multiple apps share authentication information.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sharing auth tokens across apps via Android's standard &lt;code&gt;AccountManager&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Sharing login information across same-signature apps through a &lt;code&gt;ContentProvider&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Groups of apps with a common account platform (cross-app login across several apps from the same company, for example)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When these combine with a design that "initializes many subsystems in parallel at startup," the same conditions line up as in this case: when the shared data breaks, every app chain-fails to launch, and a single reinstall won't fix it. If your own app group meets these two conditions, it's worth checking once how you guarantee consistency across a device migration, and how you degrade or regenerate when the sharing source breaks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;If you run into apps not launching after switching phones, first try "if a single clear doesn't fix it, clear the group of related apps." That's the shortest fix from the user's side. When the sharing source is broken, you have to wipe the source along with the rest.&lt;/p&gt;

&lt;p&gt;From the design side, the three pitfalls this trace surfaced are worth remembering: parallel init can backfire on shared resources; auth tends to become an implicit critical path for every feature; and ownership of shared data goes adrift during migration. Each is a "well-intentioned design" on its own, yet combined they produce an app that won't start.&lt;/p&gt;

&lt;p&gt;"Please reinstall" only holds up as a universal fix for designs contained within a single app. For apps that hold shared data and carry a complex startup sequence, the same symptom recurs even after reinstalling. Just knowing this one structure makes a real difference in how fast you respond the next time you hit the same incident.&lt;/p&gt;

</description>
      <category>android</category>
      <category>adb</category>
      <category>mobile</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>It's not too late! Make your AWS Security Agent debut with a code review!</title>
      <dc:creator>NaoyukiFujita</dc:creator>
      <pubDate>Sat, 30 May 2026 00:45:11 +0000</pubDate>
      <link>https://dev.to/aws-builders/its-not-too-late-make-your-aws-security-agent-debut-with-a-code-review-5egk</link>
      <guid>https://dev.to/aws-builders/its-not-too-late-make-your-aws-security-agent-debut-with-a-code-review-5egk</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;This article is an English translation of the article at the following URL, which was originally written in Japanese. The screenshots are still in Japanese. Sorry about that.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://qiita.com/amarelo_n24/items/e196b74f718c750a0e18" rel="noopener noreferrer"&gt;https://qiita.com/amarelo_n24/items/e196b74f718c750a0e18&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The penetration testing feature for AWS Security Agent (hereinafter referred to only as "Security Agent"), which was announced at AWS re:Invent 2025, has been generally available (GA). Code review and design review are still in preview as of May 25th, so those who haven't been able to try Security Agent yet can still try these features. I wasn't able to try penetration testing during the preview period , so I decided to at least experience code review and made my Security Agent debut!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This article reflects the author's personal views. It is based on personal testing and should be used for reference only. Furthermore, the author has no experience in app development, so the terminology used may not be entirely accurate. Any corrections or errors in the content would be greatly appreciated.&lt;br&gt;
This article was written based on information as of May 25, 2026.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  What is a Security Agent?
&lt;/h1&gt;

&lt;p&gt;As mentioned above, this service was announced during AWS re:Invent 2025. It is a frontier agent that proactively protects applications throughout the entire development lifecycle in all environments (quoted from the official AWS page).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/security-agent/" rel="noopener noreferrer"&gt;https://aws.amazon.com/security-agent/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It includes three features that became generally available (GA) in April: penetration testing, design review, and code review (the subject of this article).&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Function name&lt;/th&gt;
&lt;th&gt;Feature Overview&lt;/th&gt;
&lt;th&gt;Status（As of 2026/5/25）&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Penetration testing&lt;/td&gt;
&lt;td&gt;Attempting to infiltrate the system from an external source to evaluate security measures.&lt;/td&gt;
&lt;td&gt;GA&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Design Review&lt;/td&gt;
&lt;td&gt;Analyze product specifications, architecture documents, and technical designs from a security risk perspective.&lt;/td&gt;
&lt;td&gt;Preview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Code Review&lt;/td&gt;
&lt;td&gt;Inspect source code and repositories to detect code-level vulnerabilities.&lt;/td&gt;
&lt;td&gt;Preview&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Code security review (hereinafter referred to as "code review") is a web application diagnostic method that falls under "SAST" (Static Application Security Testing). It is considered a vulnerability assessment that checks for flaws in the source code during the development phase before it is deployed in a test environment, and detects vulnerabilities visible at the code level.&lt;/p&gt;

&lt;h1&gt;
  
  
  Security Agent Code Review
&lt;/h1&gt;

&lt;p&gt;From here, I will describe the steps to enable the Security Agent and run a code review.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enable Security Agent
&lt;/h2&gt;

&lt;p&gt;To start using Security Agent, you first need to enable it. Incidentally, simply enabling Security Agent will not incur any charges.&lt;/p&gt;

&lt;p&gt;① Click [Set up AWS Security Agent]&lt;/p&gt;

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

&lt;p&gt;② Enter [Agent Space name].&lt;br&gt;
③ Specify [User access configuration].&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you have enabled AWS Organizations and also enabled IAM Identity Center, you might want to select "Single sign-on (SSO) with IAM Identity Center." I chose this option because I also run a one-person organization. Even if you haven't enabled Organizations yet, this might be a good opportunity to try out a one-person organization.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;④ Enter a service role name. If there is no suitable role available in your account, a new service role will be created.&lt;br&gt;
⑤ If you want to use KMS encryption, check the encryption option checkbox. If the default encryption is sufficient, uncheck the checkbox.&lt;br&gt;
⑥ Set tags as needed.&lt;br&gt;
⑦ Click [Set up AWS Security Agent].&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmrcpkw27crkf217dz7lw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmrcpkw27crkf217dz7lw.png" alt=" " width="800" height="361"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;⑧ Once you see a message indicating that the application has been successfully enabled, the Security Agent has been successfully activated.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftk52by6jymcnolv2oxx5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftk52by6jymcnolv2oxx5.png" alt=" " width="800" height="153"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Enable Code Review
&lt;/h1&gt;

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

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

&lt;p&gt;③ Select "Create a new account" and "GitHub," then click "Next."&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5msgw7heyf4v3z1qmz9a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5msgw7heyf4v3z1qmz9a.png" alt=" " width="728" height="287"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;④ Click "Open AWS Security Agent on GitHub".&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff3nsn42l8fz5h19z9dxu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff3nsn42l8fz5h19z9dxu.png" alt=" " width="799" height="389"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;⑤ You will be redirected to the GitHub page. Click "Install".&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs1i40g97a3fmr5064z0f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs1i40g97a3fmr5064z0f.png" alt=" " width="800" height="290"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;⑥ Click on the GitHub account that contains the repository where you want to install the AWS Security Agent GitHub App.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frfkkapt3mztqfdw5ccvy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frfkkapt3mztqfdw5ccvy.png" alt=" " width="800" height="374"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;⑦ Click "Only select repositories" and select the repository where you want to install the GitHub App from the "Select repositories" dropdown menu.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft37zemp28twzpsmjrtf3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft37zemp28twzpsmjrtf3.png" alt=" " width="639" height="842"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;⑧ Click "Install," and the setup is complete when a screen like the one below appears.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjftau9a43jpaq5zm4nf5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjftau9a43jpaq5zm4nf5.png" alt=" " width="800" height="397"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;⑨ Return to the following screen and click "Add" again.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpk6igbwztrrzz0l6b02y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpk6igbwztrrzz0l6b02y.png" alt=" " width="798" height="174"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;⑩ Select the added integration and click "Next".&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F69nfqdqzoiomjfwzhgxc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F69nfqdqzoiomjfwzhgxc.png" alt=" " width="720" height="301"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;⑪ Select the GitHub repository name and click "Next".&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fncm2j8utw4uekccx2kcl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fncm2j8utw4uekccx2kcl.png" alt=" " width="793" height="172"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;⑫ Select the features to enable. If you want to perform code reviews, enable "Code review comments". If you want to automatically remediate detected vulnerabilities, enable "Automatic remediation". Click Connect and confirm that "Integration resource added" is displayed.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhjr1bq6xoxubuufwpdbw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhjr1bq6xoxubuufwpdbw.png" alt=" " width="800" height="146"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F91na7z7m9o70xw7raqzy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F91na7z7m9o70xw7raqzy.png" alt=" " width="800" height="318"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;⑬ Select the code review settings and click "Next". I selected "Security requirements and vulnerability detection results," which is selected by default.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5y1ato54xapqrrt76qct.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5y1ato54xapqrrt76qct.png" alt=" " width="800" height="150"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;⑭ If you want to obtain application operation logs in CloudWatch Logs, select the log group where you want to store the logs (you need to create the log group beforehand).&lt;br&gt;
⑮ Create a role for service access. If you have already created one, click "Use existing service role". If the default role is acceptable, click "Create default role".&lt;br&gt;
⑯ Click "Save".&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F28514c91f6sj2izdrkt0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F28514c91f6sj2izdrkt0.png" alt=" " width="799" height="376"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This time, we created a default role, but I think it's necessary to create a role with carefully considered policy settings. I'll investigate what policies are necessary in the future.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;⑰When it displays as shown below and "Ready" appears in the code review section, code review is enabled.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwpt3nrh83fuam1bk0fco.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwpt3nrh83fuam1bk0fco.png" alt=" " width="799" height="154"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Application User Settings
&lt;/h1&gt;

&lt;p&gt;Add an IAM Identity Center user to allow code reviews from the application.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For testing purposes, you can access it with "Administrator Access" without creating a user, but since administrators don't usually perform vulnerability assessments in normal operation, we'll configure a user even for testing purposes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;① Return to the "Agent Spaces" top page and click "Add Users" from the "Web App" tab.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk9qlus8t2v26l6j6vcp6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk9qlus8t2v26l6j6vcp6.png" alt=" " width="799" height="222"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;② Select the IAM Identity Center username you want to allow access to the Security Agent web app and click "Add users".&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frup1ukh5qxnsfupzmk92.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frup1ukh5qxnsfupzmk92.png" alt=" " width="800" height="199"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;③ Once the message indicating that the user has been added is displayed, click the Agent Web App URL.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo068d12kuz6bapa5o6wn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo068d12kuz6bapa5o6wn.png" alt=" " width="800" height="339"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;④ When the following screen appears, click "Sign in" or wait a moment, and you will be redirected to the Agent Web App screen.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6tpddkdp49m307aeyjtf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6tpddkdp49m307aeyjtf.png" alt=" " width="626" height="425"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;⑤ The screen will display as shown below, and you should confirm that the created Agent Space name is displayed.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsjws5rzoa39nawl6nbc6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsjws5rzoa39nawl6nbc6.png" alt=" " width="800" height="219"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Running the Code Review
&lt;/h1&gt;

&lt;p&gt;Now, we will run the code review.&lt;/p&gt;

&lt;p&gt;① From the Agent Web App home screen, click "Create a code review."&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmadbym7777vnyx90ps35.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmadbym7777vnyx90ps35.png" alt=" " width="800" height="404"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;② Enter a title for the code review.&lt;br&gt;
③ Select the previously connected GitHub repository, the created service role, and the CloudWatch log group, and click "Create a code review."&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkmsjvvclcl2rjobegibg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkmsjvvclcl2rjobegibg.png" alt=" " width="800" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;④ Once the message indicating that the code review has been created is displayed, click "Start review." A confirmation screen will appear, so click "Start review" again.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnyzjo5ofgjjd35dqkzpn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnyzjo5ofgjjd35dqkzpn.png" alt=" " width="800" height="199"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm0erw4xv83da8qchrfhg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm0erw4xv83da8qchrfhg.png" alt=" " width="599" height="160"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;⑤ The message "Code review started" will be displayed. Reloading the screen will display "In progress."&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqo2abdfi5gh1ytqhf6r1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqo2abdfi5gh1ytqhf6r1.png" alt=" " width="799" height="209"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffr8kvqch1lfyc8hpoomz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffr8kvqch1lfyc8hpoomz.png" alt=" " width="800" height="188"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;⑥ Clicking on the created code review will show the progress. Wait until completion.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F13gkc9dnzfj4zxa5b8s5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F13gkc9dnzfj4zxa5b8s5.png" alt=" " width="800" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Code Review Results
&lt;/h1&gt;

&lt;p&gt;This time it was completed in about an hour.&lt;/p&gt;

&lt;p&gt;① Once completed, you can view the code review results.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj0buiv16stw4go6d1dmo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj0buiv16stw4go6d1dmo.png" alt=" " width="800" height="319"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fit0ywdrqt6ku5eov5mbi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fit0ywdrqt6ku5eov5mbi.png" alt=" " width="800" height="365"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;② The scan results are displayed as follows. Well-known vulnerabilities such as SQL injection, cross-site scripting, and path traversal were detected.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Although it says "Completed," it remained showing "Finalizing" for some reason.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;h1&gt;
  
  
  Downloading Code Review Results
&lt;/h1&gt;

&lt;p&gt;You can download the code review results as a PDF file. This is likely for requesting corrections or sharing information with developers who do not have an AWS account, or for storing it as evidence.&lt;/p&gt;

&lt;p&gt;① Click "Generate Report" in the upper right corner of the code review results screen.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl3g9wcqpre9crflb1cls.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl3g9wcqpre9crflb1cls.png" alt=" " width="800" height="150"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;② Edit the extraction criteria and click "Generate and Download." The code review results will be output as a PDF file to your PC's download folder.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F769lut9mr0xuimyu7oq4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F769lut9mr0xuimyu7oq4.png" alt=" " width="597" height="639"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Automatic Remediation of Detected Vulnerabilities
&lt;/h1&gt;

&lt;p&gt;Detected vulnerabilities need to be fixed. It is possible to fix them automatically instead of manually.&lt;/p&gt;

&lt;p&gt;① Select the vulnerability you want to automatically fix and click "Fix Code."&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhj0l3xfd8nnr7yn2rlac.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhj0l3xfd8nnr7yn2rlac.png" alt=" " width="800" height="372"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;If "Automatic remediation" is not enabled, the following error will appear. In the GitHub repository's features management, turn on the "Automatic remediation" toggle button and save.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6lykvcdakmw7u35n8rqb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6lykvcdakmw7u35n8rqb.png" alt=" " width="800" height="331"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F28mx26yxmlvvif0ibva6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F28mx26yxmlvvif0ibva6.png" alt=" " width="800" height="152"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;③ Scroll to the bottom of the screen to see the detection results for the selected vulnerability. The code remediation status will be displayed. Once the fix is ​​complete and the status changes to "COMPLETED," a pull request is sent to GitHub.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F96z18k32h7wgacazc9ig.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F96z18k32h7wgacazc9ig.png" alt=" " width="651" height="300"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffx50iob706m0xgru51m9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffx50iob706m0xgru51m9.png" alt=" " width="525" height="116"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;④ Opening the pull request reveals that it was automatically created by Security Agent and details of the changes. If there are no issues with the content, merge it.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjt3o6gzsrmb13c5h7zk5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjt3o6gzsrmb13c5h7zk5.png" alt=" " width="800" height="368"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Impressions of Conducting a Code Review with Security Agent
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Recognizing the Importance of SAST
&lt;/h2&gt;

&lt;p&gt;This code review detected many types of vulnerabilities. It's probably difficult to uncover all vulnerabilities through human code reviews alone. I believe it's an important service that complements human code reviews by inspecting for remaining vulnerabilities. Furthermore, I realized that web application security testing should not only utilize external attack-based testing methods like DAST (Dynamic Application Security Testing), but also SAST, which identifies vulnerabilities at the code level and provides a starting point for fixes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Completely Eliminating Human Reviews is Not Yet Possible
&lt;/h2&gt;

&lt;p&gt;I realized that Security Agent doesn't completely replace human code reviews.&lt;/p&gt;

&lt;p&gt;After running the automatic fix and then performing another code review, the fixed vulnerabilities were not re-detected, but several new vulnerabilities were detected. It's possible that a detection method was added during the initial code review, or that it was a false positive, but it's also possible that a fix in one place affected the entire code or even the entire repository.&lt;/p&gt;

&lt;p&gt;As such, the results of the review and the recommended fixes for vulnerabilities are not always optimal for the whole system. Furthermore, there's a risk that applying automated fixes too readily could break the entire application. I think that unless people carefully review the fixes and decide whether to automate or manual fixes, it could lead to unnecessary work being done.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wouldn't it be great if it could be integrated with CodeCommit?
&lt;/h2&gt;

&lt;p&gt;As of May 21, 2026, it's not possible to target CodeCommit repositories for code reviews. It was truly surprising that S3 could be targeted for code reviews, but CodeCommit couldn't. Currently, if a user of CodeCommit wants to perform code reviews with the Security Agent, they would have to either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Store source code files in S3 and perform code reviews there&lt;/li&gt;
&lt;li&gt;Migrate the repository to GitHub.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Storing files in S3 is troublesome, and migrating to GitHub doesn't seem practical. I think it would be great if it could integrate with CodeCommit to easily perform code reviews.&lt;/p&gt;

&lt;p&gt;CodeCommit was such a valuable service that it was shut down once before returning to GA (General Availability), so I thought it was a bit of a shame that it couldn't be integrated. I guess we can only hope for future AWS updates.&lt;/p&gt;

&lt;h1&gt;
  
  
  Finally
&lt;/h1&gt;

&lt;p&gt;It's been almost six months since re:Invent 2025, but I finally got to try out Security Agent. Penetration testing, once GA is available, has become difficult to implement at an individual level. The preview period doesn't last forever. I strongly felt that you should try it as soon as possible after the announcement.&lt;/p&gt;

&lt;p&gt;You can still relatively easily experience Security Agent through code reviews, which are still in preview, so it's not too late! Why not make your Security Agent debut with a code review and use it as an opportunity to learn about web application security?&lt;/p&gt;

&lt;p&gt;Also, since design reviews are still in preview, I plan to try those out soon as well.&lt;/p&gt;

&lt;p&gt;I hope this article is helpful to someone. Thank you for reading to the end!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>security</category>
      <category>securityagent</category>
    </item>
    <item>
      <title>Here's why your Prompt is WRONG 😑</title>
      <dc:creator>Pravesh Sudha</dc:creator>
      <pubDate>Fri, 29 May 2026 17:48:43 +0000</pubDate>
      <link>https://dev.to/aws-builders/heres-why-your-prompt-is-wrong-3dlm</link>
      <guid>https://dev.to/aws-builders/heres-why-your-prompt-is-wrong-3dlm</guid>
      <description>&lt;p&gt;The right prompt is no longer just a skill — it is becoming a necessity in this fast-paced world where almost everything is driven by AI chatbots and agents.&lt;/p&gt;

&lt;p&gt;A lot of people think AI gives bad results because the model is not powerful enough, but in most cases, the real issue is the prompt itself.&lt;/p&gt;

&lt;p&gt;In today’s blog, we will uncover some of the most useful AI prompting techniques that can help you write better prompts and get significantly better results.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Zero-Shot Prompting
&lt;/h2&gt;

&lt;p&gt;This is the most common prompting technique among beginners. Almost everyone starts from this approach.&lt;/p&gt;

&lt;p&gt;In Zero-Shot Prompting, you directly ask the AI what you need in a brief and specific way without giving any prior examples.&lt;/p&gt;

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

&lt;p&gt;Instead of writing:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Generate me a Kubernetes Deployment file”&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;You can write:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Generate ONLY a Kubernetes Deployment file”&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;This small change cuts out unnecessary explanations, extra commands, and long guides that AI models often generate by default.&lt;/p&gt;

&lt;p&gt;Zero-Shot Prompting works best for popular or familiar use cases where the AI already has strong contextual understanding.&lt;/p&gt;

&lt;p&gt;Another advantage of this approach is lower token usage. In the screenshots above, you can notice that the token usage is almost 30% lower compared to longer prompts. This becomes extremely important in large organizations where APIs frequently interact with AI systems at scale.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Few-Shot Prompting
&lt;/h2&gt;

&lt;p&gt;In this approach, before giving the actual task, we first provide the AI with a few examples.&lt;/p&gt;

&lt;p&gt;This helps the model become context-aware and understand the expected style, structure, or format of the output.&lt;/p&gt;

&lt;p&gt;Few-Shot Prompting is especially useful when organizations want responses to follow a particular standard rather than simply generating the “ideal” answer.&lt;/p&gt;

&lt;p&gt;For example, if a company wants all incident reports, YAML files, or summaries to follow a fixed structure, giving examples beforehand helps maintain consistency.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For Fun: I have attached a SuperHero Example&lt;/p&gt;
&lt;/blockquote&gt;

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




&lt;h2&gt;
  
  
  3. Multi-Shot Prompting
&lt;/h2&gt;

&lt;p&gt;Multi-Shot Prompting is very similar to Few-Shot Prompting, but instead of providing a few examples, we provide many examples for even better contextual understanding.&lt;/p&gt;

&lt;p&gt;The advantage is usually better and more refined output quality.&lt;/p&gt;

&lt;p&gt;However, the downside is increased token consumption because the input becomes significantly larger due to additional examples.&lt;/p&gt;

&lt;p&gt;This is a tradeoff between output quality and cost efficiency.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Chain of Thought Prompting
&lt;/h2&gt;

&lt;p&gt;Chain of Thought Prompting encourages the AI to break down complex reasoning tasks into intermediate steps before generating the final answer.&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;“Explain why this deployment failed in a step-by-step manner.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This technique is extremely useful for debugging, analysis, problem-solving, and learning deeply about a topic instead of just scratching the surface.&lt;/p&gt;

&lt;p&gt;It is especially beneficial for curious minds who want to understand &lt;em&gt;why&lt;/em&gt; something happened rather than simply receiving the final answer.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. RAG (Retrieval-Augmented Generation)
&lt;/h2&gt;

&lt;p&gt;RAG is not exactly a prompting technique, but more of a workflow approach used alongside prompting.&lt;/p&gt;

&lt;p&gt;In this method, the AI is connected to external data sources such as databases, PDFs, internal documents, or APIs.&lt;/p&gt;

&lt;p&gt;Instead of relying solely on its internal training data, the model retrieves relevant information from these external sources before generating a response.&lt;/p&gt;

&lt;p&gt;This helps produce more accurate, contextual, and up-to-date answers.&lt;/p&gt;

&lt;p&gt;RAG is widely used in AI agents, enterprise chatbots, documentation assistants, and knowledge-based systems.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Writing an efficient prompt is not as difficult as it seems.&lt;/p&gt;

&lt;p&gt;It simply involves understanding which technique works best for your specific use case.&lt;/p&gt;

&lt;p&gt;Experiment with different prompting approaches, observe the outputs, and gradually build your own prompting style.&lt;/p&gt;

&lt;p&gt;If you liked this blog, make sure to follow me on &lt;a href="https://www.linkedin.com/in/pravesh-sudha/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;, &lt;a href="https://x.com/praveshstwt" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;, and &lt;a href="https://www.youtube.com/@pravesh-sudha" rel="noopener noreferrer"&gt;YouTube&lt;/a&gt; where I regularly share my learnings around AI, DevOps, and technology.&lt;/p&gt;

&lt;p&gt;Till then,&lt;/p&gt;

&lt;p&gt;Adios 👋&lt;/p&gt;

</description>
      <category>ai</category>
      <category>promptengineering</category>
      <category>programming</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Trying AWS IoT Core's New GetConnection and ListSubscriptions APIs</title>
      <dc:creator>ほうき星</dc:creator>
      <pubDate>Fri, 29 May 2026 14:10:41 +0000</pubDate>
      <link>https://dev.to/aws-builders/trying-aws-iot-cores-new-getconnection-and-listsubscriptions-apis-3f8g</link>
      <guid>https://dev.to/aws-builders/trying-aws-iot-cores-new-getconnection-and-listsubscriptions-apis-3f8g</guid>
      <description>&lt;p&gt;This article is a machine translation of the contents of the following URL, which I wrote in Japanese:&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://qiita.com/h0uk1st4r/items/5d44ac768059b6ecc4b9" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fqiita-user-contents.imgix.net%2Fhttps%253A%252F%252Fqiita-user-contents.imgix.net%252Fhttps%25253A%25252F%25252Fcdn.qiita.com%25252Fassets%25252Fpublic%25252Farticle-ogp-background-afbab5eb44e0b055cce1258705637a91.png%253Fixlib%253Drb-4.1.1%2526w%253D1200%2526blend64%253DaHR0cHM6Ly9xaWl0YS11c2VyLXByb2ZpbGUtaW1hZ2VzLmltZ2l4Lm5ldC9odHRwcyUzQSUyRiUyRnFpaXRhLWltYWdlLXN0b3JlLnMzLmFwLW5vcnRoZWFzdC0xLmFtYXpvbmF3cy5jb20lMkYwJTJGMzg3MDU5MSUyRnByb2ZpbGUtaW1hZ2VzJTJGMTc3Mzg0NjY4Mz9peGxpYj1yYi00LjEuMSZhcj0xJTNBMSZmaXQ9Y3JvcCZtYXNrPWVsbGlwc2UmYmc9RkZGRkZGJmZtPXBuZzMyJnM9YTkzNmVjN2ExNWM4OWUxZjA4ZmIyN2IyNjBjZWQzMTY%2526blend-x%253D120%2526blend-y%253D467%2526blend-w%253D82%2526blend-h%253D82%2526blend-mode%253Dnormal%2526s%253D906111cc8720b94f5bcc516e754c3fa4%3Fixlib%3Drb-4.1.1%26w%3D1200%26fm%3Djpg%26mark64%3DaHR0cHM6Ly9xaWl0YS11c2VyLWNvbnRlbnRzLmltZ2l4Lm5ldC9-dGV4dD9peGxpYj1yYi00LjEuMSZ3PTk2MCZoPTMyNCZ0eHQ9QVdTJTIwSW9UJTIwQ29yZSUyMCVFNSU4RCU5OCVFNCVCRCU5MyVFMyU4MSVBNyUyME1RVFQlMjAlRTMlODIlQUYlRTMlODMlQTklRTMlODIlQTQlRTMlODIlQTIlRTMlODMlQjMlRTMlODMlODglRTMlODElQUUlRTYlOEUlQTUlRTclQjYlOUElRTclOEElQjYlRTYlODUlOEIlRTMlODIlOTIlRTUlOEYlOTYlRTUlQkUlOTclRTMlODElQTclRTMlODElOEQlRTMlODIlOEIlRTMlODIlODglRTMlODElODYlRTMlODElQUIlRTMlODElQUElRTMlODElQTMlRTMlODElOUYlRTMlODElQUUlRTMlODElQTclRTglQTklQTYlRTMlODElOTkmdHh0LWFsaWduPWxlZnQlMkN0b3AmdHh0LWNvbG9yPSUyMzFFMjEyMSZ0eHQtZm9udD1IaXJhZ2lubyUyMFNhbnMlMjBXNiZ0eHQtc2l6ZT01NiZ0eHQtcGFkPTAmcz0zM2Q3OTBjNDQ2NzkxOThkMmI4ZTExOTdlYjQ3NTA0Nw%26mark-x%3D120%26mark-y%3D112%26blend64%3DaHR0cHM6Ly9xaWl0YS11c2VyLWNvbnRlbnRzLmltZ2l4Lm5ldC9-dGV4dD9peGxpYj1yYi00LjEuMSZ3PTgzOCZoPTU4JnR4dD0lNDBoMHVrMXN0NHImdHh0LWNvbG9yPSUyMzFFMjEyMSZ0eHQtZm9udD1IaXJhZ2lubyUyMFNhbnMlMjBXNiZ0eHQtc2l6ZT0zNiZ0eHQtcGFkPTAmcz0xYWQ2MDNlMmUyZDdhZjdhODcxODRiOWExYTVmM2E0Zg%26blend-x%3D242%26blend-y%3D480%26blend-w%3D838%26blend-h%3D46%26blend-fit%3Dcrop%26blend-crop%3Dleft%252Cbottom%26blend-mode%3Dnormal%26s%3D1a51a1afb9614ce1f26398e6facc61f4" height="630" class="m-0" width="1200"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://qiita.com/h0uk1st4r/items/5d44ac768059b6ecc4b9" rel="noopener noreferrer" class="c-link"&gt;
            AWS IoT Core 単体で MQTT クライアントの接続状態を取得できるようになったので試す #awsIoT - Qiita
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            はじめに こんにちは、ほうき星 @H0ukiStar です。 本日 AWS IoT Core に以下のようなアップデートがありました。 Posted on: May 28, 2026 Today, AWS IoT Core launches two new MQTT c...
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.qiita.com%2Fassets%2Ffavicons%2Fpublic%2Fproduction-c620d3e403342b1022967ba5e3db1aaa.ico" width="120" height="120"&gt;
          qiita.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


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

&lt;p&gt;Hello, I'm &lt;a href="https://x.com/H0ukiStar" rel="noopener noreferrer"&gt;@H0ukiStar&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Today, AWS IoT Core announced the following update.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Posted on: May 28, 2026&lt;br&gt;
Today, AWS IoT Core launches two new MQTT connection management APIs, GetConnection and ListSubscriptions, enabling you to easily access MQTT client connection and subscription information for your Internet of Things (IoT) devices. These APIs help you troubleshoot connectivity issues, monitor client behavior, and audit connection patterns across your device fleet.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/about-aws/whats-new/2026/05/aws-iot-core-apis-mqtt/" rel="noopener noreferrer"&gt;https://aws.amazon.com/about-aws/whats-new/2026/05/aws-iot-core-apis-mqtt/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Previously, checking device connection status required using Fleet Indexing or implementing your own state management with features such as Device Shadow. With this update, it is now possible to check device connection status directly using AWS IoT Core alone.&lt;/p&gt;

&lt;p&gt;I previously introduced methods for checking connection status using Fleet Indexing and Device Shadow in the following article, so feel free to check it out as well.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://qiita.com/h0uk1st4r/items/e80c2a47766e71974de6" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fqiita-user-contents.imgix.net%2Fhttps%253A%252F%252Fqiita-user-contents.imgix.net%252Fhttps%25253A%25252F%25252Fcdn.qiita.com%25252Fassets%25252Fpublic%25252Farticle-ogp-background-afbab5eb44e0b055cce1258705637a91.png%253Fixlib%253Drb-4.1.1%2526w%253D1200%2526blend64%253DaHR0cHM6Ly9xaWl0YS11c2VyLXByb2ZpbGUtaW1hZ2VzLmltZ2l4Lm5ldC9odHRwcyUzQSUyRiUyRnFpaXRhLWltYWdlLXN0b3JlLnMzLmFwLW5vcnRoZWFzdC0xLmFtYXpvbmF3cy5jb20lMkYwJTJGMzg3MDU5MSUyRnByb2ZpbGUtaW1hZ2VzJTJGMTc3Mzg0NjY4Mz9peGxpYj1yYi00LjEuMSZhcj0xJTNBMSZmaXQ9Y3JvcCZtYXNrPWVsbGlwc2UmYmc9RkZGRkZGJmZtPXBuZzMyJnM9YTkzNmVjN2ExNWM4OWUxZjA4ZmIyN2IyNjBjZWQzMTY%2526blend-x%253D120%2526blend-y%253D467%2526blend-w%253D82%2526blend-h%253D82%2526blend-mode%253Dnormal%2526s%253D906111cc8720b94f5bcc516e754c3fa4%3Fixlib%3Drb-4.1.1%26w%3D1200%26fm%3Djpg%26mark64%3DaHR0cHM6Ly9xaWl0YS11c2VyLWNvbnRlbnRzLmltZ2l4Lm5ldC9-dGV4dD9peGxpYj1yYi00LjEuMSZ3PTk2MCZoPTMyNCZ0eHQ9QVdTJTIwSW9UJTIwQ29yZSUyMCVFMyU4MSVBNyVFMyU4MyVBMiVFMyU4MyU4RSVFMyU4MSVBRSVFNiU4RSVBNSVFNyVCNiU5QSVFNyU4QSVCNiVFNiU4NSU4QiVFMyU4MiU5MiVFMyU4MiVBQSVFMyU4MyVCMyVFMyU4MyU4NyVFMyU4MyU5RSVFMyU4MyVCMyVFMyU4MyU4OSVFMyU4MSVBNyVFNSU4RiU5NiVFNSVCRSU5NyVFMyU4MSU5OSVFMyU4MiU4QjIlRTMlODElQTQlRTMlODElQUUlRTYlOTYlQjklRTYlQjMlOTUmdHh0LWFsaWduPWxlZnQlMkN0b3AmdHh0LWNvbG9yPSUyMzFFMjEyMSZ0eHQtZm9udD1IaXJhZ2lubyUyMFNhbnMlMjBXNiZ0eHQtc2l6ZT01NiZ0eHQtcGFkPTAmcz0wYjNhMDBjMWZhNThiYmY5MDJiZWQzMmEwNzIwOWI3Nw%26mark-x%3D120%26mark-y%3D112%26blend64%3DaHR0cHM6Ly9xaWl0YS11c2VyLWNvbnRlbnRzLmltZ2l4Lm5ldC9-dGV4dD9peGxpYj1yYi00LjEuMSZ3PTgzOCZoPTU4JnR4dD0lNDBoMHVrMXN0NHImdHh0LWNvbG9yPSUyMzFFMjEyMSZ0eHQtZm9udD1IaXJhZ2lubyUyMFNhbnMlMjBXNiZ0eHQtc2l6ZT0zNiZ0eHQtcGFkPTAmcz0xYWQ2MDNlMmUyZDdhZjdhODcxODRiOWExYTVmM2E0Zg%26blend-x%3D242%26blend-y%3D480%26blend-w%3D838%26blend-h%3D46%26blend-fit%3Dcrop%26blend-crop%3Dleft%252Cbottom%26blend-mode%3Dnormal%26s%3Df91f7c61308188c3fc094bcb8ff62c7f" height="630" class="m-0" width="1200"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://qiita.com/h0uk1st4r/items/e80c2a47766e71974de6" rel="noopener noreferrer" class="c-link"&gt;
            AWS IoT Core でモノの接続状態をオンデマンドで取得する2つの方法 #lambda - Qiita
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            はじめに こんにちは、ほうき星です。 皆さんは、AWS IoT Core に接続した自作 IoT デバイス（Raspberry Pi / ESP32 など）が 「今オンラインなのか、それともオフラインなのか」を確認したいと思ったことはありませんか？ 私は自作した IoT ...
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.qiita.com%2Fassets%2Ffavicons%2Fpublic%2Fproduction-c620d3e403342b1022967ba5e3db1aaa.ico" width="120" height="120"&gt;
          qiita.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;h3&gt;
  
  
  Trying It Out
&lt;/h3&gt;

&lt;p&gt;Let's try the new APIs.&lt;/p&gt;

&lt;p&gt;At the time of writing, these APIs do not appear to be available in the AWS SDK yet, so the API requests must be signed manually using SigV4.&lt;/p&gt;

&lt;p&gt;This time, I used &lt;code&gt;awscurl&lt;/code&gt; for signing. You can install it with 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;pip &lt;span class="nb"&gt;install &lt;/span&gt;awscurl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You can also retrieve the API endpoint using the AWS CLI as shown below.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws iot describe-endpoint &lt;span class="nt"&gt;--endpoint-type&lt;/span&gt; iot:Data-ATS

&lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"endpointAddress"&lt;/span&gt;: &lt;span class="s2"&gt;"abcd1234567890-ats.iot.ap-northeast-1.amazonaws.com"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  GetConnection
&lt;/h3&gt;

&lt;p&gt;This API allows you to retrieve information such as the connection status and Keep Alive configuration of a specified MQTT client.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://docs.aws.amazon.com/iot/latest/developerguide/mqtt.html#get-connection-api" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;docs.aws.amazon.com&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;Send the following request using &lt;code&gt;awscurl&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;awscurl &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--service&lt;/span&gt; iotdevicegateway &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; ap-northeast-1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://abcd1234567890-ats.iot.ap-northeast-1.amazonaws.com/connections/&amp;lt;clientId&amp;gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;[!WARNING]&lt;br&gt;
For SigV4 signing, you must specify &lt;code&gt;iotdevicegateway&lt;/code&gt; as the service name instead of &lt;code&gt;iot&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can retrieve information such as whether the client is currently connected and when the connection was established.&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;"connected"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"cleanSession"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"clientId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"clientId"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"thingName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"thingName"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"keepAliveDuration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"connectedSince"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1780061470032&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;You can also retrieve socket information by adding &lt;code&gt;includeSocketInformation=true&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;awscurl &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--service&lt;/span&gt; iotdevicegateway &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; ap-northeast-1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://abcd1234567890-ats.iot.ap-northeast-1.amazonaws.com/connections/&amp;lt;clientId&amp;gt;?includeSocketInformation=true"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;"connected"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"cleanSession"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"clientId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"clientId"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"thingName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"thingName"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"sourceIp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sourceIp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"sourcePort"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;sourcePort&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"targetIp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"targetIp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"targetPort"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8883&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"keepAliveDuration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"connectedSince"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1780061470032&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;h2&gt;
  
  
  ListSubscriptions
&lt;/h2&gt;

&lt;p&gt;This API allows you to retrieve the topics subscribed to by a specified client.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://docs.aws.amazon.com/iot/latest/developerguide/mqtt.html#list-subscriptions-api" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;docs.aws.amazon.com&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;Send the following request using &lt;code&gt;awscurl&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;awscurl &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--service&lt;/span&gt; iotdevicegateway &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; ap-northeast-1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://abcd1234567890-ats.iot.ap-northeast-1.amazonaws.com/connections/&amp;lt;clientId&amp;gt;/subscriptions"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;[!WARNING]&lt;br&gt;
For SigV4 signing, you must specify &lt;code&gt;iotdevicegateway&lt;/code&gt; as the service name instead of &lt;code&gt;iot&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can retrieve the topics currently subscribed to by the specified client as 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;"nextToken"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"subscriptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"topicFilter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$aws/things/&amp;lt;thingName&amp;gt;/shadow/update/delta"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"qos"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&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;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;With the newly introduced GetConnection and ListSubscriptions APIs, it is now possible to retrieve MQTT client connection status and subscription information directly from AWS IoT Core.&lt;/p&gt;

&lt;p&gt;Previously, checking connection status often required custom implementations using Fleet Indexing, Device Shadow, or Lifecycle Events. With these new APIs, monitoring client state should become much simpler.&lt;/p&gt;

&lt;p&gt;Although the APIs do not yet appear to be available in the AWS SDK, they can already be used today by manually signing requests with SigV4.&lt;/p&gt;

&lt;p&gt;If you're interested, give them a try.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>iotcore</category>
    </item>
    <item>
      <title>Hackez votre AWS CLI pour ajouter le support CloudShell et transformer votre terminal en bastion</title>
      <dc:creator>Paul SANTUS</dc:creator>
      <pubDate>Fri, 29 May 2026 12:43:23 +0000</pubDate>
      <link>https://dev.to/aws-builders/hackez-votre-aws-cli-pour-ajouter-le-support-cloudshell-et-transformer-votre-terminal-en-bastion-2hoo</link>
      <guid>https://dev.to/aws-builders/hackez-votre-aws-cli-pour-ajouter-le-support-cloudshell-et-transformer-votre-terminal-en-bastion-2hoo</guid>
      <description>&lt;p&gt;J'utilise AWS CloudShell depuis la Console depuis un moment. C'est pratique : un shell pré-authentifié dans votre navigateur, directement dans la Console AWS. Mais je me suis toujours demandé : pourquoi je ne peux pas l'utiliser depuis mon terminal ? Pourquoi n'y a-t-il pas de commande &lt;code&gt;aws cloudshell&lt;/code&gt; ?&lt;/p&gt;

&lt;p&gt;Il s'avère que c'est possible. L'API existe, elle n'est simplement pas publique. Et une fois que vous avez accès à CloudShell en CLI, vous pouvez faire des choses intéressantes avec, comme utiliser un CloudShell attaché à un VPC comme bastion pour atteindre vos instances RDS privées.&lt;/p&gt;

&lt;p&gt;Consultez le &lt;a href="https://github.com/psantus/cloudshell-cli" rel="noopener noreferrer"&gt;dépôt compagnon&lt;/a&gt; en lisant cet article.&lt;/p&gt;

&lt;h2&gt;
  
  
  CloudShell : une API non documentée
&lt;/h2&gt;

&lt;p&gt;AWS CloudShell n'a pas de support officiel SDK ou CLI. Mais la Console doit bien communiquer avec &lt;em&gt;quelque chose&lt;/em&gt;, non ? En regardant ce que fait le navigateur quand vous ouvrez CloudShell, vous pouvez rétro-ingénierer l'API.&lt;/p&gt;

&lt;p&gt;Heureusement, &lt;a href="https://github.com/guyon-it-consulting/cloudshell-boto3" rel="noopener noreferrer"&gt;Jérôme Guyon&lt;/a&gt; a déjà fait ce travail et publié un modèle de service compatible boto3. Son travail a rendu tout cela possible.&lt;/p&gt;

&lt;p&gt;L'API est simple : créer des environnements, les démarrer/arrêter, créer des sessions, uploader/télécharger des fichiers. Le mécanisme de session utilise le protocole WebSocket de SSM sous le capot, ce qui signifie que &lt;code&gt;session-manager-plugin&lt;/code&gt; (le même binaire qui fait tourner &lt;code&gt;aws ssm start-session&lt;/code&gt;) peut se connecter aux sessions CloudShell.&lt;/p&gt;

&lt;h2&gt;
  
  
  Apprendre un nouveau tour à l'AWS CLI
&lt;/h2&gt;

&lt;p&gt;L'AWS CLI a une fonctionnalité peu connue : &lt;code&gt;aws configure add-model&lt;/code&gt;. Donnez-lui un modèle de service JSON, et soudain la CLI connaît un nouveau service. AWS utilise ça en interne pour les previews privées.&lt;/p&gt;

&lt;p&gt;(Le modèle boto3 du dépôt de Jérôme a juste besoin d'un champ &lt;code&gt;"version": "2.0"&lt;/code&gt; ajouté au niveau racine pour devenir compatible CLI.)&lt;/p&gt;

&lt;p&gt;Exécutez :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws configure add-model &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--service-model&lt;/span&gt; file://cloudshell-cli-model.json &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--service-name&lt;/span&gt; cloudshell
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;C'est tout. Maintenant j'ai &lt;code&gt;aws cloudshell&lt;/code&gt; avec l'auto-complétion et tout :&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;aws cloudshell &lt;span class="nb"&gt;help

&lt;/span&gt;AVAILABLE COMMANDS
       create-environment
       create-session
       delete-environment
       describe-environments
       get-environment-status
       start-environment
       stop-environment
       ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Se connecter à CloudShell depuis le terminal
&lt;/h2&gt;

&lt;p&gt;Le workflow est simple :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Créer ou trouver un environnement&lt;/span&gt;
aws cloudshell create-environment &lt;span class="nt"&gt;--region&lt;/span&gt; eu-west-1

&lt;span class="c"&gt;# Attendre qu'il soit RUNNING&lt;/span&gt;
aws cloudshell get-environment-status &lt;span class="nt"&gt;--environment-id&lt;/span&gt; &amp;lt;ID&amp;gt; &lt;span class="nt"&gt;--region&lt;/span&gt; eu-west-1

&lt;span class="c"&gt;# Créer une session et se connecter&lt;/span&gt;
session-manager-plugin &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;aws cloudshell create-session &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--environment-id&lt;/span&gt; &amp;lt;ID&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--session-type&lt;/span&gt; TMUX &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--tab-id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;uuidgen | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="s1"&gt;'[:upper:]'&lt;/span&gt; &lt;span class="s1"&gt;'[:lower:]'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--q-cli-disabled&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; eu-west-1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'{SessionId:SessionId,TokenValue:TokenValue,StreamUrl:StreamUrl}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; json&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; eu-west-1 StartSession
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Et vous y êtes. Un shell complet sur une instance CloudShell, depuis votre terminal. Pas besoin de navigateur.&lt;/p&gt;

&lt;h2&gt;
  
  
  Le problème des credentials
&lt;/h2&gt;

&lt;p&gt;Il y a un hic. Quand vous utilisez CloudShell depuis la Console, AWS injecte vos credentials automatiquement via un appel API &lt;code&gt;PutCredentials&lt;/code&gt;. Celui-ci utilise votre token de session console (l'auth par cookie de votre connexion navigateur) pour alimenter le endpoint de métadonnées du conteneur en credentials temporaires.&lt;/p&gt;

&lt;p&gt;Quand vous vous connectez par programme, ça ne se fait pas. Le endpoint de credentials du conteneur renvoie une erreur 500. Vous devez injecter les credentials vous-même :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Exécutez localement, puis collez la sortie dans votre session CloudShell&lt;/span&gt;
aws configure export-credentials &lt;span class="nt"&gt;--profile&lt;/span&gt; my-profile &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="nb"&gt;env&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pas idéal, mais ça fonctionne.&lt;/p&gt;

&lt;h2&gt;
  
  
  Le cas d'usage bastion
&lt;/h2&gt;

&lt;p&gt;C'est là que ça devient intéressant. Vous pouvez créer un environnement CloudShell attaché à un VPC :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws cloudshell create-environment &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--environment-name&lt;/span&gt; db-access &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--vpc-config&lt;/span&gt; &lt;span class="s1"&gt;'{
    "VpcId": "vpc-abc123",
    "SubnetIds": ["subnet-private-1"],
    "SecurityGroupIds": ["sg-allowed-by-rds"]
  }'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; eu-west-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mettez-le dans le même security group que celui autorisé par votre RDS, et soudain vous pouvez vous connecter à votre base de données directement depuis le shell :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mysql &lt;span class="nt"&gt;-h&lt;/span&gt; my-instance.xxx.eu-west-1.rds.amazonaws.com &lt;span class="nt"&gt;-u&lt;/span&gt; admin &lt;span class="nt"&gt;-p&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pas d'instance EC2 bastion à maintenir. Pas de clés SSH à gérer. Pas de coût horaire quand vous ne l'utilisez pas (CloudShell est gratuit). L'environnement se suspend après 20 minutes d'inactivité et vous pouvez le maintenir en vie avec &lt;code&gt;aws cloudshell send-heart-beat&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ce qui ne marche pas (et j'ai essayé..)
&lt;/h2&gt;

&lt;p&gt;J'ai passé pas mal de temps à essayer de faire fonctionner CloudShell comme un vrai bastion de port-forwarding, pour pouvoir utiliser des outils locaux comme DBeaver contre un RDS distant à travers lui. Voici ce que j'ai trouvé :&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Le port forwarding basé sur SSM ne fonctionne pas.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;ECS, par exemple, enregistre les conteneurs comme cibles SSM. Son identifiant SSM n'est pas documenté mais une fois qu'on le connaît, ça marche bien, comme je l'ai décrit dans &lt;a href="https://dev.to/aws-builders/access-your-aws-database-using-local-port-forwarding-on-your-ecsfargate-container-4nk4"&gt;un précédent article&lt;/a&gt;. De cette façon vous pouvez lancer &lt;code&gt;aws ssm start-session --document-name AWS-StartPortForwardingSessionToRemoteHost&lt;/code&gt;.&lt;br&gt;
Les notebooks SageMaker ont un comportement similaire.&lt;/p&gt;

&lt;p&gt;Les instances/conteneurs CloudShell ne semblent pas être enregistrés comme instances managées SSM. Ou s'ils le sont, c'est caché et à ce jour, personne chez AWS n'a divulgué le format de leur ID :) J'ai essayé toutes les combinaisons d'ID d'environnement, d'ID de session et de format de préfixe auxquelles j'ai pu penser. Aucune ne fonctionne.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Le port forwarding local à travers le PTY ne fonctionne pas non plus.&lt;/strong&gt; La session est un terminal, pas un flux TCP brut. Vous ne pouvez pas faire passer des données binaires du protocole MySQL à travers. J'ai même essayé de mettre en place un relais ncat à l'intérieur de CloudShell et de tunneler à travers la session. Le relais fonctionne bien en interne, mais il n'y a aucun moyen de l'exposer comme un port TCP local sur votre machine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Le hole punching UDP est théoriquement possible&lt;/strong&gt; mais nécessite que le CloudShell ait accès à internet (NAT Gateway sur son subnet), et même là vous vous battez contre des problèmes de symétrie NAT des deux côtés. J'ai réussi à faire fonctionner STUN depuis CloudShell, mais le hole punch complet est fragile et impraticable pour un usage en production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Alors à quoi ça sert ?
&lt;/h2&gt;

&lt;p&gt;Honnêtement, à pas mal de choses :&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Accès rapide à la base de données&lt;/strong&gt; sans maintenir une instance EC2 bastion. Connectez-vous, exécutez vos requêtes, déconnectez-vous. Gratuit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatisation.&lt;/strong&gt; Vous pouvez scripter l'exécution de commandes sur CloudShell via Python + &lt;code&gt;session-manager-plugin&lt;/code&gt;. Utile pour exécuter des choses à l'intérieur d'un VPC sans déployer une Lambda ou une tâche Fargate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Débogage de connectivité réseau.&lt;/strong&gt; Lancez un CloudShell dans une combinaison subnet/SG spécifique et testez ce qui peut atteindre quoi.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transfert de fichiers&lt;/strong&gt; (depuis les environnements publics). Les APIs &lt;code&gt;get-file-upload-urls&lt;/code&gt; et &lt;code&gt;get-file-download-urls&lt;/code&gt; vous donnent des URLs S3 présignées.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;La limitation principale est que vous êtes limité à exécuter des commandes &lt;em&gt;à l'intérieur&lt;/em&gt; du shell. Vous ne pouvez pas l'utiliser comme un tunnel transparent pour vos outils locaux. Pour ça, vous avez toujours besoin d'une instance EC2 avec l'agent SSM, ou d'une tâche ECS avec execute-command activé.&lt;/p&gt;

&lt;h2&gt;
  
  
  Essayez vous-même
&lt;/h2&gt;

&lt;p&gt;J'ai publié le modèle et un script d'exemple ici : &lt;a href="https://github.com/psantus/cloudshell-cli" rel="noopener noreferrer"&gt;github.com/psantus/cloudshell-cli&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;L'installation se fait en une commande. Le tout est un seul fichier JSON qui apprend un nouveau service à votre AWS CLI. Rappelez-vous juste : c'est une API non documentée. AWS peut la modifier ou la casser à tout moment. Ne construisez rien de critique dessus.&lt;/p&gt;

&lt;p&gt;Mais pour un accès VPC rapide depuis votre terminal ? C'est plutôt génial.&lt;/p&gt;

</description>
      <category>cloudshell</category>
      <category>cli</category>
      <category>aws</category>
    </item>
    <item>
      <title>Générer des données structurées avec un LLM : quelques astuces pour plus de fiabilité</title>
      <dc:creator>Paul SANTUS</dc:creator>
      <pubDate>Fri, 29 May 2026 12:41:31 +0000</pubDate>
      <link>https://dev.to/aws-builders/generer-des-donnees-structurees-avec-un-llm-quelques-astuces-pour-plus-de-fiabilite-1png</link>
      <guid>https://dev.to/aws-builders/generer-des-donnees-structurees-avec-un-llm-quelques-astuces-pour-plus-de-fiabilite-1png</guid>
      <description>&lt;p&gt;Les LLMs sont excellents pour générer du texte. Ils sont mauvais pour générer des données structurées de manière fiable. Si vous avez déjà essayé de faire produire à un agent un objet JSON avec un schéma précis, vous connaissez le douloureux résultat : champs manquants, clés hallucinées, types incohérents, et des sorties qui cassent votre pipeline en aval.&lt;/p&gt;

&lt;p&gt;Dépassant le stade du code de démo pour travailler sur de vraies applications IA en production, j'ai été confronté au problème et j'ai trouvé une approche qui fonctionne remarquablement bien pour une application IA que je développe : &lt;strong&gt;utiliser les outils comme le pattern Builder de la programmation orientée objet&lt;/strong&gt;. Au lieu de demander au modèle de produire un blob JSON final, vous lui donnez des outils qui construisent la sortie de manière incrémentale - comme appeler des méthodes sur un objet. Le modèle ne voit ni ne produit jamais la structure finale directement. Il appelle simplement des outils, et la sortie structurée émerge comme un effet de bord.&lt;/p&gt;

&lt;p&gt;C'est particulièrement important quand votre agent traite des documents volumineux (formulaires d'assurance, dossiers juridiques, dossiers médicaux) qui consomment la majeure partie de la fenêtre de contexte. Quand l'entrée est volumineuse et que la tâche comporte plusieurs étapes, vous ne pouvez pas vous permettre de réserver aussi de l'espace pour une sortie structurée massive à la fin. Le pattern accumulateur vous permet de compresser la conversation en cours de route sans perdre aucune des données structurées déjà collectées, car ces données vivent entièrement en dehors de la fenêtre de contexte.&lt;/p&gt;

&lt;h2&gt;
  
  
  Défis
&lt;/h2&gt;

&lt;h3&gt;
  
  
  "Génère-moi un gros JSON" : les soucis
&lt;/h3&gt;

&lt;p&gt;L'approche naïve - demander au modèle de produire une structure JSON complète - échoue de manière quasi systématique lorsque le volume augmente :&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Dérive de schéma.&lt;/strong&gt; Le modèle oublie des champs obligatoires, en invente de nouveaux, ou change les types d'une exécution à l'autre. Un champ &lt;code&gt;date&lt;/code&gt; peut être une chaîne une fois et un objet la suivante.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tout-ou-rien.&lt;/strong&gt; Si le modèle fait une seule erreur dans une sortie JSON de 200 lignes, l'ensemble est impossible à parser. Vous devez soit relancer toute la génération, soit écrire du code de correction fragile.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pas de progrès incrémental.&lt;/strong&gt; Quand un agent doit collecter des informations &lt;em&gt;et&lt;/em&gt; produire une sortie structurée, lui demander de faire les deux en une seule passe signifie qu'il ne peut pas itérer. Il s'engage sur une structure avant d'avoir tous les faits.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Pourquoi &lt;code&gt;response_format&lt;/code&gt; et les schémas de function-calling ne suffisent pas
&lt;/h3&gt;

&lt;p&gt;Les modes de sortie structurée (comme &lt;code&gt;response_format: json_schema&lt;/code&gt; d'OpenAI ou les schémas de résultats d'outils de Bedrock) aident avec la syntaxe - vous obtiendrez du JSON valide. Mais ils ne résolvent pas le problème sémantique. Le modèle doit toujours produire la structure entière en une seule passe, et il hallucine toujours du contenu pour remplir les champs obligatoires.&lt;/p&gt;

&lt;h3&gt;
  
  
  Un problème répandu
&lt;/h3&gt;

&lt;p&gt;Toute équipe qui construit des agents autonomes ou semi-autonomes fait face à ce problème, pas seulement moi. Kiro CLI, le compagnon de développement agentique d'AWS, par exemple, a beaucoup galéré avec les grandes structures de données à son lancement.&lt;/p&gt;

&lt;p&gt;Depuis, ses mainteneurs ont équipé son harnais de capacités JSON (manipulations &lt;code&gt;jq&lt;/code&gt;, par exemple) et de multiples stratégies (utilisation extensive de grep, glob, tail..) pour éviter de remplir la fenêtre de contexte.&lt;/p&gt;

&lt;p&gt;Ça fait quand même plaisir de savoir que je ne suis pas le seul à avoir galéré :)&lt;/p&gt;

&lt;h2&gt;
  
  
  Mes solutions
&lt;/h2&gt;

&lt;p&gt;Voici quelques astuces que j'ai utilisées avec succès pour contrôler à la fois la sortie de l'agent et la fenêtre de contexte. Comme je ne prétends pas avoir toutes les recettes, n'hésitez pas à commenter les vôtres ou à me taguer dans vos propres posts :)&lt;/p&gt;

&lt;h3&gt;
  
  
  Utiliser les outils comme des Builder méthodes
&lt;/h3&gt;

&lt;p&gt;L'idée centrale : définir des outils qui agissent comme des méthodes Builder en POO. Chaque appel d'outil ajoute un élément bien typé à un accumulateur. Le travail du modèle passe de "produis cette structure" à "appelle ces fonctions dans le bon ordre."&lt;/p&gt;

&lt;p&gt;Voici le pattern - imaginez un agent qui traite des sinistres d'assurance en lisant des documents et en construisant une évaluation structurée :&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;tool&lt;/span&gt;

&lt;span class="c1"&gt;# L'accumulateur - c'est votre sortie structurée
&lt;/span&gt;&lt;span class="n"&gt;claim_output&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;parties&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;events&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;damages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;evidence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;assessment&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;reset_output&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;claim_output&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;assessment&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;parties&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;events&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;damages&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;evidence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;claim_output&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;


&lt;span class="nd"&gt;@tool&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_party&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;role&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;policy_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Enregistrer une partie impliquée dans le sinistre.

    Args:
        name: Nom complet de la personne ou de l&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;organisation.
        role: Un parmi : claimant, insured, witness, adjuster, third_party
        policy_id: Numéro de police si applicable.

    Returns:
        Confirmation avec les détails de la partie.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;role&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claimant&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;insured&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;witness&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;adjuster&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;third_party&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error: invalid role &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;. Must be one of: claimant, insured, witness, adjuster, third_party&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="n"&gt;claim_output&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;parties&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&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="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;policy_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;policy_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Added &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;role&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;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;span class="nd"&gt;@tool&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;date&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;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Enregistrer un événement chronologique pertinent pour le sinistre.

    Args:
        description: Ce qui s&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;est passé (1-3 phrases).
        date: Date au format ISO (AAAA-MM-JJ).
        location: Où cela s&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;est produit (optionnel).
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;claim_output&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;events&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;description&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="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;location&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Recorded event on &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;date&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="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;claim_output&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;events&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; events total)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;span class="nd"&gt;@tool&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_damage&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="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;evidence_ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Enregistrer un poste de dommage avec le coût estimé.

    Args:
        item: Description de l&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;élément endommagé ou du coût.
        amount: Coût estimé en dollars.
        category: Un parmi : property, medical, liability, lost_income
        evidence_ref: Référence à une preuve justificative (optionnel).
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;property&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;medical&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;liability&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;lost_income&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error: invalid category &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="n"&gt;claim_output&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;damages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;item&lt;/span&gt;&lt;span class="sh"&gt;"&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;amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;category&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;evidence_ref&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;evidence_ref&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;claim_output&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;damages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Added damage: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;item&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;amount&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;). Running total: $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;L'agent reçoit ces outils et un prompt système qui lui dit de traiter un sinistre. Au fur et à mesure qu'il lit les documents et découvre des informations, il appelle &lt;code&gt;add_party&lt;/code&gt;, &lt;code&gt;add_event&lt;/code&gt; et &lt;code&gt;add_damage&lt;/code&gt;. La sortie structurée se construit de manière incrémentale.&lt;/p&gt;

&lt;h3&gt;
  
  
  Validation à la frontière
&lt;/h3&gt;

&lt;p&gt;Chaque appel d'outil est un point de contrôle de validation. Vous pouvez rejeter les entrées invalides immédiatement :&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="nd"&gt;@tool&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_damage&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="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;evidence_ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;property&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;medical&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;liability&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;lost_income&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error: invalid category &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error: amount must be positive, got &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;evidence_ref&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;evidence_ref&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;claim_output&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;evidence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]]:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error: evidence &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;evidence_ref&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; not registered. Call add_evidence first.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="c1"&gt;# ...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Le modèle reçoit un feedback instantané. S'il essaie de référencer une preuve qu'il n'a pas encore enregistrée, l'outil le lui dit. Le modèle se corrige au tour suivant. Comparez cela à la validation d'un blob JSON de 500 lignes après coup - à ce moment-là, le modèle est passé à autre chose et ne peut plus corriger ses erreurs dans le contexte.&lt;/p&gt;

&lt;h3&gt;
  
  
  Décorréler la phase de réflexion de la construction de la sortie
&lt;/h3&gt;

&lt;p&gt;Un avantage clé : le même agent peut avoir des outils de &lt;em&gt;lecture&lt;/em&gt; et des outils d'&lt;em&gt;écriture&lt;/em&gt;. Les outils de lecture récupèrent et explorent les données. Les outils d'écriture construisent la sortie. Le modèle les entrelace naturellement :&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;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;model&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="n"&gt;prompt&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="c1"&gt;# Outils de lecture
&lt;/span&gt;        &lt;span class="n"&gt;read_document&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;search_policy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;get_weather_report&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;# Outils d'écriture (méthodes Builder)
&lt;/span&gt;        &lt;span class="n"&gt;add_party&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;add_event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;add_damage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;add_evidence&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;set_assessment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;# Suivi de progression
&lt;/span&gt;        &lt;span class="n"&gt;mark_step_done&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="c1"&gt;# Un seul appel - l'agent lit les documents ET construit la sortie structurée
&lt;/span&gt;&lt;span class="nf"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Process this claim: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;claim_text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# La sortie est prête
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;claim_output&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Le modèle lit un rapport de police, extrait une partie, lit une facture médicale, enregistre un poste de dommage, vérifie la police d'assurance, et ainsi de suite. Recherche et construction de la sortie sont entrelacées plutôt que séquentielles.&lt;/p&gt;

&lt;h3&gt;
  
  
  Suivi de progression et récupération
&lt;/h3&gt;

&lt;p&gt;Parce que la sortie s'accumule de manière incrémentale, vous obtenez la récupération après crash gratuitement :&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;STEPS&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;1. Identify all parties&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;2. Establish timeline of events&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;3. Catalog damages with evidence&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;4. Cross-reference policy coverage&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;5. Produce assessment&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;completed_steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

&lt;span class="nd"&gt;@tool&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;mark_step_done&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;step_number&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Marquer une étape de traitement comme terminée.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;completed_steps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;step_number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;remaining&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;STEPS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&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;i&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;completed_steps&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&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;Step &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;step_number&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; done. Remaining: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;remaining&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Si l'agent atteint une limite de fenêtre de contexte ou plante, vous avez déjà des résultats partiels - chaque partie identifiée, chaque événement enregistré, chaque poste de dommage catalogué jusqu'à ce point. Vous pouvez reprendre ou utiliser ce que vous avez.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gestion du contexte par injection d'état
&lt;/h3&gt;

&lt;p&gt;C'est là que ce pattern prend tout son sens. Quand votre agent ingère un document de 30 pages puis fait des dizaines d'appels d'outils pour récupérer des sources supplémentaires, la fenêtre de contexte se remplit vite. Dans une approche traditionnelle, vous perdriez votre sortie structurée en même temps que la conversation quand vous atteignez la limite. Mais parce que l'accumulateur vit dans la mémoire Python - pas dans l'historique des messages - vous pouvez compresser agressivement la conversation sans perdre un seul point de données.&lt;/p&gt;

&lt;p&gt;Un gestionnaire de conversation personnalisé (une possibilité offerte, par exemple, par le &lt;a href="https://strandsagents.com/docs/user-guide/concepts/agents/conversation-management/#creating-a-conversationmanager" rel="noopener noreferrer"&gt;SDK Strands Agents&lt;/a&gt;) remplace les anciens messages par un résumé d'état compact dérivé de l'accumulateur :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ClaimConversationManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ConversationManager&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;apply_management&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;

        &lt;span class="c1"&gt;# Garder le premier message + les 2 derniers messages
&lt;/span&gt;        &lt;span class="c1"&gt;# Remplacer tout le reste par un résumé d'état
&lt;/span&gt;        &lt;span class="n"&gt;first_msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;recent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;

        &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_build_state_summary&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;state_msg&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;role&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;user&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;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[STATE]&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s"&gt;Continue.&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;messages&lt;/span&gt;&lt;span class="p"&gt;[:]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;first_msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;state_msg&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;recent&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_build_state_summary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Résumer ce qui a été fait en utilisant l&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;état de l&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;accumulateur.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&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;claim_output&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;parties&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="n"&gt;parties&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&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;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;claim_output&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;parties&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
            &lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Parties: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parties&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;claim_output&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;damages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;claim_output&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;damages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Damages: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;claim_output&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;damages&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; items, $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; total&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;claim_output&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;events&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Events: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;claim_output&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;events&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; recorded&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Parce que la sortie structurée vit en Python (pas dans la conversation), la compression du contexte ne perd aucune donnée. Le modèle peut toujours voir ce qu'il a déjà produit en lisant le résumé d'état.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bénéfices
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Sûreté de typage sans coercition
&lt;/h3&gt;

&lt;p&gt;Chaque outil a des paramètres typés imposés par le framework. Le modèle doit fournir une &lt;code&gt;category&lt;/code&gt; parmi &lt;code&gt;property, medical, liability, lost_income&lt;/code&gt; - non pas parce que vous parsez du JSON et vérifiez après coup, mais parce que la signature de l'outil l'exige. Les appels invalides sont rejetés avec des messages d'erreur clairs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Composabilité
&lt;/h3&gt;

&lt;p&gt;Les outils se composent naturellement. Vous pouvez ajouter de nouveaux champs de sortie en ajoutant de nouveaux outils sans modifier les existants. Vous voulez suivre les pièces justificatives ? Ajoutez un outil &lt;code&gt;add_evidence&lt;/code&gt;. Vous voulez une recommandation finale ? Ajoutez un outil &lt;code&gt;set_assessment&lt;/code&gt;. Le modèle découvre les nouvelles capacités via sa liste d'outils.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testabilité
&lt;/h3&gt;

&lt;p&gt;Chaque outil est une fonction pure (ou presque). Vous pouvez les tester unitairement de manière indépendante :&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;test_add_damage_rejects_invalid_category&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nf"&gt;reset_output&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;add_damage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Roof repair&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cosmetic&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;claim_output&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;damages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_add_damage_tracks_total&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nf"&gt;reset_output&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;add_damage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Roof repair&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;property&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;add_damage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Water damage&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;property&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;claim_output&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;damages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;claim_output&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;damages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;7000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Schéma de sortie déterministe
&lt;/h3&gt;

&lt;p&gt;Le schéma de sortie est défini par votre code Python, pas par l'interprétation du modèle d'un prompt. &lt;code&gt;claim_output&lt;/code&gt; a toujours les mêmes clés avec les mêmes types. Les consommateurs en aval peuvent compter sur la structure de manière inconditionnelle.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dégradation gracieuse
&lt;/h3&gt;

&lt;p&gt;Si le modèle manque de contexte ou rencontre une erreur, vous avez tout ce qu'il a produit jusqu'à ce point. Vous pouvez même détecter une sortie vide et relancer avec un coup de pouce :&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;try&lt;/span&gt;&lt;span class="p"&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;claim_text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;claim_output&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;parties&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;claim_output&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;events&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="nf"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You haven&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t started processing. Begin by identifying the parties involved.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Comportement naturel de l'agent
&lt;/h3&gt;

&lt;p&gt;Le modèle n'a pas besoin de basculer entre "réfléchir" et "formater." Il réfléchit en appelant des outils. La sortie structurée est un sous-produit du travail de l'agent, pas un fardeau de formatage supplémentaire ajouté par-dessus.&lt;/p&gt;

&lt;p&gt;Ce pattern - outils comme Builder, accumulateur comme sortie, validation à la frontière - est la manière la plus fiable que j'ai trouvée pour obtenir des données structurées d'un workflow agentique. Ça fonctionne parce que c'est aligné avec la façon dont les modèles à appels d'outils se comportent déjà : ils raisonnent, ils agissent, ils observent les résultats, et ils agissent à nouveau. Vous faites simplement en sorte que "agir" signifie "construire un morceau de la sortie."&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>agents</category>
    </item>
    <item>
      <title>LLMs suck at generating large, structured data. Tips on how to get your AI agent to do it reliably</title>
      <dc:creator>Paul SANTUS</dc:creator>
      <pubDate>Fri, 29 May 2026 12:06:19 +0000</pubDate>
      <link>https://dev.to/aws-builders/llms-suck-at-generating-large-structured-data-tips-on-how-to-get-your-ai-agent-to-do-it-reliably-3mop</link>
      <guid>https://dev.to/aws-builders/llms-suck-at-generating-large-structured-data-tips-on-how-to-get-your-ai-agent-to-do-it-reliably-3mop</guid>
      <description>&lt;p&gt;LLMs are great at generating text. They're terrible at generating structured data reliably. If you've ever tried to get an agent to produce a JSON object with a specific schema, you know the pain: missing fields, hallucinated keys, inconsistent types, and outputs that break your downstream pipeline.&lt;/p&gt;

&lt;p&gt;As I got past toy examples and labs to work on real, production-grade AI apps, I faced the problem and found an approach that works remarkably well for an AI app I'm building: &lt;strong&gt;use tools like object-oriented programming Builder pattern&lt;/strong&gt;. Instead of asking the model to produce a final JSON blob, you give it tools that incrementally build the output - like calling methods on an object. The model never sees or produces the final structure directly. It just calls functions, and the structured output emerges as a side effect.&lt;/p&gt;

&lt;p&gt;This matters especially when your agent processes large documents (like insurance forms, legal filings, medical records) that eat up most of the context window. When the input is big and the task is multi-step, you can't afford to also reserve space for a massive structured output at the end. The accumulator pattern lets you compress the conversation mid-flight without losing any of the structured data you've already collected, because that data lives outside the token window entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The "generate JSON" problem
&lt;/h3&gt;

&lt;p&gt;The naive approach - asking a model to output a complete JSON structure - fails in predictable ways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Schema drift.&lt;/strong&gt; The model forgets required fields, invents new ones, or changes types between runs. A &lt;code&gt;date&lt;/code&gt; field might be a string one time and an object the next.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;All-or-nothing failure.&lt;/strong&gt; If the model makes one mistake in a 200-line JSON output, the entire thing is unparseable. You either retry the whole generation or write brittle fixup code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No incremental progress.&lt;/strong&gt; If the model hits a context limit or stops mid-generation, you lose everything. There's no partial result to recover from.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Hallucination in structure.&lt;/strong&gt; Models are more likely to hallucinate when producing structured output in one shot. They fill in fields they're uncertain about rather than leaving them empty, because the structure demands completeness.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Coupling research and output.&lt;/strong&gt; When an agent needs to gather information &lt;em&gt;and&lt;/em&gt; produce structured output, asking it to do both in one pass means it can't iterate. It commits to a structure before it has all the facts.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Why &lt;code&gt;response_format&lt;/code&gt; and function-calling schemas aren't enough
&lt;/h3&gt;

&lt;p&gt;Structured output modes (like OpenAI's &lt;code&gt;response_format: json_schema&lt;/code&gt; or Bedrock's tool result schemas) help with syntax - you'll get valid JSON. But they don't solve the semantic problem. The model still has to produce the entire structure in one shot, and it still hallucinates content to fill required fields.&lt;/p&gt;

&lt;h3&gt;
  
  
  A wide-spread issue
&lt;/h3&gt;

&lt;p&gt;Any team building autonomous or semi-autonomous agents face this, not just me. Kiro CLI, AWS' agentic dev companion, for instance, struggled hard with large data structures when first launched. &lt;/p&gt;

&lt;p&gt;Since then, its maintainers have equipped its harness with JSON capabilities (&lt;code&gt;jq&lt;/code&gt; manipulations, for instance) and multiples strategies (extensive use of grep, glob, tail..) to avoid filling the context window.&lt;/p&gt;

&lt;p&gt;Still, happy to know I'm not alone in facing this :)&lt;/p&gt;

&lt;h2&gt;
  
  
  My solutions
&lt;/h2&gt;

&lt;p&gt;Here are a few tricks I have used successfully to control both agent output and context window. As I don't claim to have all the recipes, don't hesitate to comment your own or tag my in your own posts :)&lt;/p&gt;

&lt;h3&gt;
  
  
  Tools as Builder methods
&lt;/h3&gt;

&lt;p&gt;The core idea: define tools that act like OOP builder methods. Each tool call adds one well-typed element to an accumulator. The model's job shifts from "produce this structure" to "call these functions in the right order."&lt;/p&gt;

&lt;p&gt;Here's the pattern - imagine an agent that processes insurance claims by reading documents and building a structured claim assessment:&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;tool&lt;/span&gt;

&lt;span class="c1"&gt;# The accumulator - this is your structured output
&lt;/span&gt;&lt;span class="n"&gt;claim_output&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;parties&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;events&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;damages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;evidence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;assessment&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;reset_output&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;claim_output&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;assessment&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;parties&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;events&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;damages&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;evidence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;claim_output&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;


&lt;span class="nd"&gt;@tool&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_party&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;role&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;policy_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Register a party involved in the claim.

    Args:
        name: Full name of the person or organization.
        role: One of: claimant, insured, witness, adjuster, third_party
        policy_id: Policy number if applicable.

    Returns:
        Confirmation with party details.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;role&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claimant&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;insured&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;witness&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;adjuster&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;third_party&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error: invalid role &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;. Must be one of: claimant, insured, witness, adjuster, third_party&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="n"&gt;claim_output&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;parties&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&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="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;policy_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;policy_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Added &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;role&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;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;span class="nd"&gt;@tool&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;date&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;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Record a chronological event relevant to the claim.

    Args:
        description: What happened (1-3 sentences).
        date: ISO date string (YYYY-MM-DD).
        location: Where it happened (optional).
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;claim_output&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;events&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;description&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="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;location&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Recorded event on &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;date&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="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;claim_output&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;events&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; events total)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;span class="nd"&gt;@tool&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_damage&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="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;evidence_ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Register a damage item with estimated cost.

    Args:
        item: Description of the damaged item or cost.
        amount: Estimated cost in dollars.
        category: One of: property, medical, liability, lost_income
        evidence_ref: Reference to supporting evidence (optional).
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;property&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;medical&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;liability&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;lost_income&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error: invalid category &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="n"&gt;claim_output&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;damages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;item&lt;/span&gt;&lt;span class="sh"&gt;"&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;amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;category&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;evidence_ref&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;evidence_ref&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;claim_output&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;damages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Added damage: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;item&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;amount&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;). Running total: $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent is given these tools and a system prompt that tells it to process a claim. As it reads documents and discovers information, it calls &lt;code&gt;add_party&lt;/code&gt;, &lt;code&gt;add_event&lt;/code&gt;, and &lt;code&gt;add_damage&lt;/code&gt;. The structured output builds up incrementally.&lt;/p&gt;

&lt;h3&gt;
  
  
  Validation at the boundary
&lt;/h3&gt;

&lt;p&gt;Each tool call is a validation checkpoint. You can reject bad input immediately:&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="nd"&gt;@tool&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_damage&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="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;evidence_ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;property&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;medical&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;liability&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;lost_income&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error: invalid category &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error: amount must be positive, got &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;evidence_ref&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;evidence_ref&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;claim_output&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;evidence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]]:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error: evidence &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;evidence_ref&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; not registered. Call add_evidence first.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="c1"&gt;# ...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model gets instant feedback. If it tries to reference evidence it hasn't registered yet, the tool tells it. The model self-corrects on the next turn. Compare this to validating a 500-line JSON blob after the fact - by then, the model has moved on and can't fix its mistakes in context.&lt;/p&gt;

&lt;h3&gt;
  
  
  Separating research from output construction
&lt;/h3&gt;

&lt;p&gt;A key benefit: the same agent can have &lt;em&gt;reading&lt;/em&gt; tools and &lt;em&gt;writing&lt;/em&gt; tools. Reading tools fetch and explore data. Writing tools construct the output. The model interleaves them naturally:&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;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;model&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="n"&gt;prompt&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="c1"&gt;# Reading tools
&lt;/span&gt;        &lt;span class="n"&gt;read_document&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;search_policy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;get_weather_report&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;# Writing tools (builder methods)
&lt;/span&gt;        &lt;span class="n"&gt;add_party&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;add_event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;add_damage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;add_evidence&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;set_assessment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;# Progress tracking
&lt;/span&gt;        &lt;span class="n"&gt;mark_step_done&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="c1"&gt;# One call - the agent reads documents AND builds structured output
&lt;/span&gt;&lt;span class="nf"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Process this claim: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;claim_text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Output is ready
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;claim_output&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model reads a police report, extracts a party, reads a medical bill, registers a damage item, cross-references the policy, and so on. Research and output construction are interleaved rather than sequential.&lt;/p&gt;

&lt;h3&gt;
  
  
  Progress tracking and recovery
&lt;/h3&gt;

&lt;p&gt;Because output accumulates incrementally, you get crash recovery for free:&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;STEPS&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;1. Identify all parties&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;2. Establish timeline of events&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;3. Catalog damages with evidence&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;4. Cross-reference policy coverage&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;5. Produce assessment&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;completed_steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

&lt;span class="nd"&gt;@tool&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;mark_step_done&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;step_number&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Mark a processing step as completed.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;completed_steps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;step_number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;remaining&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;STEPS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&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;i&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;completed_steps&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&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;Step &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;step_number&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; done. Remaining: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;remaining&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the agent hits a context window limit or errors out, you already have partial results - every party identified, every event recorded, every damage item cataloged up to that point. You can resume or use what you have.&lt;/p&gt;

&lt;h3&gt;
  
  
  Context management with state injection
&lt;/h3&gt;

&lt;p&gt;Here's where this pattern really pays off. When your agent ingests a 30-page document and then makes dozens of tool calls to fetch additional sources, the context window fills up fast. In a traditional approach, you'd lose your structured output along with the conversation when you hit the limit. But because the accumulator lives in Python memory - not in the message history - you can aggressively compress the conversation without losing a single data point.&lt;/p&gt;

&lt;p&gt;A custom conversation manager (a possibility offered, for instance, by the &lt;a href="https://strandsagents.com/docs/user-guide/concepts/agents/conversation-management/#creating-a-conversationmanager" rel="noopener noreferrer"&gt;Strands Agents SDK&lt;/a&gt;) replaces old messages with a compact state summary derived from the accumulator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ClaimConversationManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ConversationManager&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;apply_management&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;

        &lt;span class="c1"&gt;# Keep first message + last 2 messages
&lt;/span&gt;        &lt;span class="c1"&gt;# Replace everything in between with a state summary
&lt;/span&gt;        &lt;span class="n"&gt;first_msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;recent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;

        &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_build_state_summary&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;state_msg&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;role&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;user&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;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[STATE]&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s"&gt;Continue.&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;messages&lt;/span&gt;&lt;span class="p"&gt;[:]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;first_msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;state_msg&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;recent&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_build_state_summary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Summarize what&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s been done using the accumulator state.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&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;claim_output&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;parties&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="n"&gt;parties&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&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;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;claim_output&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;parties&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
            &lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Parties: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parties&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;claim_output&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;damages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;claim_output&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;damages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Damages: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;claim_output&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;damages&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; items, $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; total&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;claim_output&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;events&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Events: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;claim_output&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;events&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; recorded&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because the structured output lives in Python (not in the conversation), context compression doesn't lose any data. The model can always see what it's already produced by reading the state summary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Type safety without type coercion
&lt;/h3&gt;

&lt;p&gt;Each tool has typed parameters enforced by the framework. The model must provide a &lt;code&gt;category&lt;/code&gt; that's one of &lt;code&gt;property, medical, liability, lost_income&lt;/code&gt; - not because you're parsing JSON and checking after the fact, but because the tool signature demands it. Invalid calls get rejected with clear error messages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Composability
&lt;/h3&gt;

&lt;p&gt;Tools compose naturally. You can add new output fields by adding new tools without changing existing ones. Want to track evidence attachments? Add an &lt;code&gt;add_evidence&lt;/code&gt; tool. Want a final recommendation? Add a &lt;code&gt;set_assessment&lt;/code&gt; tool. The model discovers new capabilities through its tool list.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testability
&lt;/h3&gt;

&lt;p&gt;Each tool is a pure function (or close to it). You can unit test them independently:&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;test_add_damage_rejects_invalid_category&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nf"&gt;reset_output&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;add_damage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Roof repair&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cosmetic&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;claim_output&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;damages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_add_damage_tracks_total&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nf"&gt;reset_output&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;add_damage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Roof repair&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;property&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;add_damage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Water damage&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;property&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;claim_output&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;damages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;claim_output&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;damages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;7000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Deterministic output schema
&lt;/h3&gt;

&lt;p&gt;The output schema is defined by your Python code, not by the model's interpretation of a prompt. &lt;code&gt;claim_output&lt;/code&gt; always has the same keys with the same types. Downstream consumers can rely on the structure unconditionally.&lt;/p&gt;

&lt;h3&gt;
  
  
  Graceful degradation
&lt;/h3&gt;

&lt;p&gt;If the model runs out of context or hits an error, you have everything it produced up to that point. You can even detect empty output and retry with a nudge:&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;try&lt;/span&gt;&lt;span class="p"&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;claim_text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;claim_output&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;parties&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;claim_output&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;events&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="nf"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You haven&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t started processing. Begin by identifying the parties involved.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Natural agent behavior
&lt;/h3&gt;

&lt;p&gt;The model doesn't have to context-switch between "thinking" and "formatting." It thinks by calling tools. The structured output is a byproduct of the agent doing its job, not an additional formatting burden layered on top.&lt;/p&gt;




&lt;p&gt;This pattern - tools as Builder, accumulator as output, validation at the boundary - has been the most reliable way I've found to get structured data out of an agentic workflow. It works because it aligns with how tool-calling models already behave: they reason, they act, they observe results, and they act again. You're just making "act" mean "build one piece of the output."&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>llm</category>
    </item>
    <item>
      <title>The 10 Commandments of Working in Production</title>
      <dc:creator>Orel Bello</dc:creator>
      <pubDate>Fri, 29 May 2026 07:59:05 +0000</pubDate>
      <link>https://dev.to/aws-builders/the-10-commandments-of-working-in-production-2hla</link>
      <guid>https://dev.to/aws-builders/the-10-commandments-of-working-in-production-2hla</guid>
      <description>&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;What scares you the most?&lt;/p&gt;

&lt;p&gt;Some say spiders, some say clowns, but what scares Engineers (both DevOps and Developers) the most is a P0 incident, where production is down.&lt;br&gt;
Want to make it even scarier? Imagine that you’re the one who’s responsible for it.&lt;/p&gt;

&lt;p&gt;When this kind of incident happens, it’s never pleasant, but is it really inevitable?&lt;br&gt;
Production incidents unfortunately happen, and at some companies, they happen more than others.&lt;/p&gt;

&lt;p&gt;They say that you can’t be a true Senior Engineer if you don’t have a few Production incidents with your name on them, but that doesn’t mean we want to break production intentionally. We’d like to avoid it as much as possible, and even though we can’t completely eliminate it, with the right methodologies, we can definitely reduce it.&lt;/p&gt;

&lt;p&gt;So, let’s learn how to do it, but first, let me introduce myself.&lt;/p&gt;

&lt;h2&gt;
  
  
  About Me
&lt;/h2&gt;

&lt;p&gt;I’m Orel Bello, an AWS Community Builder and a passionate DevOps Engineer with over 4 years of experience, including the past 3 years at Melio. My tech journey began during my military service as a Deputy Commander in the Technological Control Center for the Israel Police. After earning a B.Sc. in Computer Science, I started as a Storage and Virtualization Engineer before discovering my true calling in DevOps.&lt;/p&gt;

&lt;p&gt;Now an AWS Certified Professional in both DevOps and Solutions Architecture, I specialize in building scalable, efficient, and cost-effective cloud solutions.&lt;/p&gt;

&lt;p&gt;So you can imagine that I have some experience as a production breaker (and also as a breakdancer, but this is for another blogpost).&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;WARNING! THESE RULES WERE WRITTEN IN BLOOD!&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Always have a rollback plan
&lt;/h2&gt;

&lt;p&gt;The first thing that you need to do when you’re touching a Production environment is have a rollback plan.&lt;/p&gt;

&lt;p&gt;Let’s say that you need to modify some resource. What if this modification will cause an outage? You need to be prepared. Like they say: Hope for the best, but prepare for the worst. Better be safe than sorry.&lt;/p&gt;

&lt;p&gt;Playbooks and documentation can save lives. So even if you’re making a small change, it’s important to prepare a rollback plan ahead of time.&lt;/p&gt;

&lt;p&gt;Are you touching the DB? Make sure to take a Snapshot before you do.&lt;br&gt;
Changing a secret, SSM Parameter, or even an IAM Policy? Make sure to save the original value in a safe place.&lt;br&gt;
The examples are endless, but the concept stays the same. Always be sure to have a rollback plan in case things get messy.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Timing is everything
&lt;/h2&gt;

&lt;p&gt;Do you have production-related work to do? If you can, always schedule it for the very beginning of the workweek, especially if you’re collaborating across time zones.That’s usually when traffic is lighter, so if your change requires downtime or carries some kind of risk, it’s safer to do it when fewer clients are actively using your system.&lt;/p&gt;

&lt;p&gt;We also have the opposite rule that completes the circle — never perform a production change right before the weekend. In just a few hours, the entire company will be offline, and trust me, you don’t want to be the one who forces people back online to fix an issue.&lt;/p&gt;

&lt;p&gt;For that same reason, the very end of a working day isn’t the best time for sensitive tasks, either.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  3. Work on Dev before Prod — gradually
&lt;/h2&gt;

&lt;p&gt;If you have no idea what a development or QA environment is, stop everything you’re doing right now and go build one.&lt;/p&gt;

&lt;p&gt;On a best practice methodology, we always want to avoid testing features in a live Production environment.&lt;/p&gt;

&lt;p&gt;It doesn’t matter if you’re working at a small company, you can never have just one environment that shares your development and your production workload. It’s a recipe for multiple outages and downtimes.&lt;/p&gt;

&lt;p&gt;It’s best to have a Development/QA environment, a Staging/Pre-Prod environment and a Production environment. And once you have those environments, you can deploy your changes gradually:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First on Development&lt;/li&gt;
&lt;li&gt;Then on Staging&lt;/li&gt;
&lt;li&gt;Only after that on Production&lt;/li&gt;
&lt;li&gt;This way you can handle errors and bugs before they make it to Production.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. A wolf in sheep’s clothing — not everything is as innocent as it seems
&lt;/h2&gt;

&lt;p&gt;It’s important to have a Production mindset and always think: is what I’m doing somehow affecting production? The answer isn’t always straightforward.&lt;/p&gt;

&lt;p&gt;There are the obvious resources that you know you should be aware of, like the Database, DNS records or your compute service that runs your core production logic (EC2, K8s, Lambda functions, you name it). But you shouldn’t let your guard down so easily when you’re working on other resources.&lt;/p&gt;

&lt;p&gt;Example (based on a true story):&lt;br&gt;
The security team gives you a list of unused IAM Roles (created by CloudFormation) for more than 180 days, and tells you to handle it. So you may think that you can delete them and no harm will be done. But when you delete them, suddenly dozens of Production CloudFormation stacks can’t be deployed anymore because you deleted a resource created by them, and now they’ve drifted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So always think twice:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is my action touching production?&lt;/li&gt;
&lt;li&gt;Am I absolutely sure about it?&lt;/li&gt;
&lt;li&gt;If I’m not sure what the resource I’m dealing with is, it’s better to be cautious and to tread lightly.&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%2Fo7jrvukv3u67gbcfugh6.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo7jrvukv3u67gbcfugh6.jpg" alt=" " width="800" height="429"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Overcome the shame — ask for help
&lt;/h2&gt;

&lt;p&gt;Oops. You did your best but you still broke prod.&lt;/p&gt;

&lt;p&gt;Take a deep breath and relax. Don’t panic. It’s unpleasant, but it will pass.&lt;/p&gt;

&lt;p&gt;You probably want to fix it ASAP, and the fewer people who know the better. But it’s important to overcome the shame and ask for help. It’s better that your manager hears it from you than from someone else.&lt;/p&gt;

&lt;p&gt;Consult with your teammates and fix it together. If you try to handle it yourself without anyone else knowing, there is a chance you can actually make it worse.&lt;/p&gt;

&lt;p&gt;Everyone makes mistakes, it’s human nature. I can guarantee you that even your CTO broke production a few times throughout his career. So don’t take it to heart, just focus on fixing it the best way you can.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Version control best practices — don’t take shortcuts
&lt;/h2&gt;

&lt;p&gt;Don’t do shortcuts.&lt;/p&gt;

&lt;p&gt;Do you have a small and completely safe change? Don’t be lazy. Open a PR and send it to a teammate to review before you deploy it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NEVER. WORK. ON. MASTER/MAIN.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Developers may be limited by repository rules, and even if they want, they can’t work directly on the Master/Main branch. But DevOps usually have Admin privileges on GitHub, so if they push directly to master, no one can stop them.&lt;/p&gt;

&lt;p&gt;Working with PRs is crucial because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CI/CD workflows may only trigger on merge, not direct pushes&lt;/li&gt;
&lt;li&gt;Without a PR, you lose review and can miss mistakes&lt;/li&gt;
&lt;li&gt;Rollbacks are harder if you work directly on master&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%2F8zj4vh8npd2rkf5lmi47.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8zj4vh8npd2rkf5lmi47.jpg" alt=" " width="552" height="387"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Root account — even scarier than a production account
&lt;/h2&gt;

&lt;p&gt;You probably know that when you’re dealing with your production environment, you should pay attention and be careful.&lt;/p&gt;

&lt;p&gt;But on the root account, you should be even more careful. The root account, if you’re using an AWS Organization, is the account that manages all the other accounts, including production.&lt;/p&gt;

&lt;p&gt;The most common encounter DevOps Engineers have with the root account is managing the SCP (Service Control Policies). If, for example, you apply an SCP to the wrong account or detach the FullAccess Policy, you can affect all the services in all the accounts at once.&lt;/p&gt;

&lt;p&gt;So if you’re not paying attention, you can cause an outage to your entire Organization without even noticing.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. IaC — don’t do anything manually
&lt;/h2&gt;

&lt;p&gt;Remember we talked about how it’s important not to be lazy? Don’t do anything manually on the AWS console.&lt;/p&gt;

&lt;p&gt;IaC (Infrastructure as Code) can help you deploy changes with ease, but sometimes it takes more time to write Terraform code for a new resource than to deploy it manually. Don’t get tempted.&lt;/p&gt;

&lt;p&gt;Why is it so important?&lt;/p&gt;

&lt;p&gt;Easier rollbacks (since the code is in a repo)&lt;br&gt;
More scalable&lt;br&gt;
Consistent across environments&lt;br&gt;
You can preview your changes with a plan before deploying&lt;/p&gt;

&lt;h2&gt;
  
  
  9. AI — powerful, but dangerous
&lt;/h2&gt;

&lt;p&gt;Today, AI is everywhere, and we can’t run from it even if we tried. And while it can be a productivity boost, unfortunately, it can also cause you an outage if you’re not careful.&lt;/p&gt;

&lt;p&gt;Whether it’s malfunctioning code that breaks your application logic, or IaC code that unintentionally deletes core resources, you need to make sure you use AI in a responsible way.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Don’t deploy untested AI-generated code to production&lt;/li&gt;
&lt;li&gt;Don’t rely solely on AI without checking documentation&lt;/li&gt;
&lt;li&gt;Don’t test AI code on production, that’s what Dev and Staging are for&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  10. Learn from your mistakes
&lt;/h2&gt;

&lt;p&gt;As much as we don’t like them and want to avoid them, production incidents are a natural part of life.&lt;/p&gt;

&lt;p&gt;If you already broke prod, try to learn from the mistake. That’s why we do retro meetings after every incident. And trust me, you won’t forget what you did that caused an outage, and that’s how you will get better.&lt;/p&gt;

&lt;p&gt;At the end of the day, production incidents are the best teachers.&lt;/p&gt;

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

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

&lt;p&gt;The harsh truth is that production incidents are here to stay, and we need to learn to live with them.&lt;/p&gt;

&lt;p&gt;But if you follow the best practices, have a “production mindset”, and always ask yourself “Is what I’m about to do affecting production functionality?” and plan your steps accordingly, you can definitely avoid many incidents and improve your entire system uptime.&lt;/p&gt;

&lt;p&gt;Got your own rule? Or your production-war-story? Please share it in the comments below!&lt;/p&gt;

</description>
      <category>sre</category>
      <category>devops</category>
      <category>aws</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Why Rate Limiting Alone Won't Stop OTP Abuse — A Real Incident Breakdown</title>
      <dc:creator>Mubarak Alhazan</dc:creator>
      <pubDate>Fri, 29 May 2026 06:46:59 +0000</pubDate>
      <link>https://dev.to/aws-builders/why-rate-limiting-alone-wont-stop-otp-abuse-a-real-incident-breakdown-dig</link>
      <guid>https://dev.to/aws-builders/why-rate-limiting-alone-wont-stop-otp-abuse-a-real-incident-breakdown-dig</guid>
      <description>&lt;h2&gt;
  
  
  The Attack That Looked Like Normal Traffic
&lt;/h2&gt;

&lt;p&gt;On a perfectly normal Tuesday morning, while the team was getting into their usual flow, we quietly noticed that our SendGrid email delivery was behaving strangely. Emails were bouncing more than usual. Open rates were dropping, and then the one that made everything serious was that legitimate users were reporting that our OTP emails weren't showing up in their inboxes. They were landing in spam.&lt;/p&gt;

&lt;p&gt;We dug into our SendGrid dashboard, expecting to find something obvious: a misconfigured domain, a broken DKIM record, maybe a sudden spike in bounces from a bad email list. What we found instead was that thousands of OTP emails had gone out to email addresses we didn't recognise. Addresses nobody on our platform had ever signed up with.&lt;/p&gt;

&lt;p&gt;Someone was abusing our OTP endpoints.&lt;/p&gt;

&lt;p&gt;The otp endpoints in question are public by design. They exist to let new users sign up via email OTP without needing an existing account. No authentication required. That openness, which is intentional and necessary for the signup flow, is exactly what made them a target.&lt;/p&gt;

&lt;p&gt;The attacker had written a script that continuously hit these endpoints with a rotating pool of random email addresses, triggering our system to send OTP emails on their behalf. Our infrastructure was being used as a free email delivery machine, and it was burning our SendGrid reputation in the process.&lt;/p&gt;

&lt;p&gt;Most of those emails landed on test carrier gateways and were dropped or bounced. But some hit real inboxes. One recipient received over 15 unsolicited OTP emails from us within two days before finally marking us as spam. Another got two within a single day and did the same. When real people mark you as spam, inbox providers like Gmail and Yahoo take notice, and that's exactly what started pushing our legitimate emails into spam folders.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why The Obvious Defences Failed
&lt;/h2&gt;

&lt;p&gt;The first instinct when you see an endpoint getting hammered is to reach for rate limiting. We already had it in place (1 burst per minute and 3 requests per hour per IP). Reasonable numbers that would stop any single bad actor cold.&lt;/p&gt;

&lt;p&gt;Except this wasn't a single bad actor.&lt;/p&gt;

&lt;p&gt;When each of 200+ proxy IPs sends exactly one request and then steps aside, rate limiting becomes completely blind to the attack. From the rate limiter's perspective, it's just seeing 200 different users each making one perfectly normal request. No threshold crossed. No alarm triggered. Just 200 OTP emails going out the door, one after another, all looking entirely legitimate.&lt;/p&gt;

&lt;p&gt;That's the thing about residential proxy networks that makes them so effective against traditional defences: they don't look like bots. These aren't datacenter IPs that show up on blocklists. They're real consumer devices on real ISP connections. When you look at those IPs in your logs, they look like your actual users.&lt;/p&gt;

&lt;p&gt;We also tried blocking known test-carrier gateways, the kind of infrastructure attackers typically use to absorb bulk emails. That helped reduce some of the noise, but it didn't stop the attack. The attacker's email pool was wide enough that plenty of addresses on real domains were still getting through, and those were the ones reaching actual inboxes and generating spam reports.&lt;/p&gt;

&lt;p&gt;Blocking the individual IPs wasn't a real option either. By the time you identify and block one, the proxy network has already rotated to the next. You'd need to block the entire residential IP ranges of major ISPs, which would mean blocking your actual users in the process.&lt;/p&gt;

&lt;p&gt;The root problem with all of these approaches is that they're built around a different threat model. They assume abuse looks like volume from a single source. This attack was the opposite: low-volume and distributed across hundreds of sources. No single data point in our logs was alarming. Only the aggregate told the story, and by the time the aggregate was obvious, the damage was already done. The question now was how to stop it, and stop it fast.&lt;/p&gt;

&lt;p&gt;We needed something that could answer a simpler question: Is there a real human on the other end of this request? Rate limiting, IP blocking, and gateway filtering can't answer that question. Only one thing can reliably answer that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stopping the Bleeding
&lt;/h2&gt;

&lt;p&gt;Before we could build a proper fix, we needed to stop the bleeding first, because every spam report that landed while we were still investigating made our reputation harder to recover. So we made an uncomfortable but necessary call: fully block both OTP signup endpoints immediately, knowing it would lock out legitimate users trying to sign up via OTP alongside the attacker.&lt;/p&gt;

&lt;p&gt;The blast radius was manageable. Other signup methods were still available, and anyone hitting the blocked endpoints got a generic message directing them to support. Not ideal, but acceptable for the short window we needed.&lt;/p&gt;

&lt;p&gt;The block bought us the breathing room to focus on the real solution that could tell the difference between a real user and an attack script.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Permanent Fix: AWS WAF CAPTCHA
&lt;/h2&gt;

&lt;p&gt;The core question we needed to answer was "&lt;em&gt;Is there a real human on the other end of this request?"&lt;/em&gt; Our rate limiting, IP blocking, and gateway filtering couldn't answer that. A well-established industry solution that can answer is &lt;strong&gt;CAPTCHA&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A script can rotate IPs, generate random emails, and fire requests all day, but it can't solve a CAPTCHA challenge. That was exactly what we needed.&lt;/p&gt;

&lt;p&gt;We implemented the fix at the AWS WAF level, sitting in front of the load balancer. Requests get challenged before they ever reach the backend, which means no OTP logic runs, no email gets triggered, and SendGrid never sees the request at all.&lt;/p&gt;

&lt;p&gt;We set up two rules working together:&lt;/p&gt;

&lt;p&gt;The first was a &lt;strong&gt;rate-based&lt;/strong&gt; block rule: any single IP exceeding a set threshold within a 10-minute window gets blocked outright. This handles the unsophisticated case where someone is just hammering the endpoint from one place.&lt;/p&gt;

&lt;p&gt;The second, and more important one, was an &lt;strong&gt;always-CAPTCHA&lt;/strong&gt; rule: every single request to the OTP endpoints gets challenged with a CAPTCHA, regardless of where it's coming from or how many requests that IP has made. This rule actually kills this specific attack. It doesn't matter that the attacker is rotating through 200+ IPs, sending one request each. None of them can solve a CAPTCHA.&lt;/p&gt;

&lt;p&gt;The two rules complement each other cleanly to solve the problem.&lt;/p&gt;

&lt;p&gt;One thing worth thinking about when implementing CAPTCHA on a high-traffic endpoint is user experience. You don't want legitimate users solving a CAPTCHA every time they request an OTP. We handled this with a 300-second immunity window: once a user solves the CAPTCHA, they're not challenged again for the next 5 minutes. This immunity time is tracked using WAF Token cookies.&lt;/p&gt;

&lt;p&gt;After deploying the changes to production, automated requests stopped reaching the backend. We could also see from the WAF dashboard that the automated requests were failing CAPTCHA challenges.&lt;/p&gt;

&lt;p&gt;There was one wrinkle, though — the AWS WAF CAPTCHA challenge itself. It uses the classic image-based approach: select all the traffic lights, pick the bicycles, and identify the buses. It works, but it's not a great experience for users who are just trying to sign up. That friction pushed us to evaluate alternatives, and we ended up switching to &lt;strong&gt;Cloudflare Turnstile&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why We Switched to Cloudflare Turnstile
&lt;/h2&gt;

&lt;p&gt;AWS WAF CAPTCHA solved the security problem, but the user experience it delivered wasn't something we were comfortable shipping long-term. Image-based challenges add real friction to what should be a simple signup flow. First impressions matter, and asking a new user to solve a puzzle before they can even get their OTP is not the experience we wanted.&lt;/p&gt;

&lt;p&gt;So we switched to Cloudflare Turnstile.&lt;/p&gt;

&lt;p&gt;Turnstile takes a fundamentally different approach. Instead of asking users to prove they're human by identifying objects in blurry images, it runs a set of browser-based signals in the background: things like how the page was loaded, JavaScript execution patterns, and other non-invasive checks. In most cases, the user sees nothing at all. They click the signup button, the check happens invisibly, and the OTP request goes through. Only when Turnstile is genuinely uncertain does it surface a checkbox challenge, and even then, it's far less disruptive than a grid of traffic light images.&lt;/p&gt;

&lt;p&gt;The implementation sits in the same place as before, in front of the OTP endpoints, intercepting requests before they reach the OTP logic. The main difference is that instead of relying on the WAF to serve the challenge, the Turnstile widget lives on the frontend and generates a token that gets verified server-side before any OTP logic runs.&lt;/p&gt;

&lt;p&gt;If you're evaluating CAPTCHA options for a similar setup, the decision mostly comes down to this: AWS WAF CAPTCHA is convenient if you're already in the AWS ecosystem and want everything managed in one place, but Turnstile is the better choice if user experience is a priority, and for a signup flow, it almost always should be.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We'd Do Differently From Day One
&lt;/h2&gt;

&lt;p&gt;The honest answer is that this incident was preventable. CAPTCHA solutions were available before the incident, but we didn't think enough about how these endpoints could be abused, only about how they were meant to be used.&lt;/p&gt;

&lt;p&gt;That's the mindset shift worth taking away from this.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Any public endpoint that triggers an external action needs abuse protection from day one.&lt;/strong&gt; OTP emails, password reset emails, SMS codes, notification triggers. If an unauthenticated request can cause your infrastructure to do something on behalf of an attacker, that endpoint is a target. For us, the consequence was a degraded sender reputation. But depending on your setup, the same attack pattern could quietly rack up significant bills. AWS SES, Twilio, and similar pay-per-use services will charge you for every single message an attacker tricks your system into sending. The vulnerability is the same regardless of the provider.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Rate limiting is necessary but not sufficient.&lt;/strong&gt; It should be the floor, not the ceiling. If rate limiting is your only line of defence on a public endpoint that sends emails, you're one residential proxy network away from this same situation. Layer it with CAPTCHA, and choose a CAPTCHA solution that balances security with user experience&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Monitor your email reputation proactively.&lt;/strong&gt; We only noticed the problem when users started complaining. By then, the damage was already accumulating. Setting up daily delivery reports and reputation alerts from your email provider takes little time and gives you early warning before a bad situation becomes a crisis.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Think about blast radius when you design public flows.&lt;/strong&gt; Because we had alternative signup methods available, blocking the affected endpoints bought us time without completely breaking the product. If those endpoints had been the only way into the platform, the trade-off would have been much harder. Designing with that kind of redundancy gives you options when something goes wrong.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finally, this incident prompted us to audit every other public endpoint that triggers an email. That audit should have happened at build time. It's now on the roadmap as a recurring security review, not a one-time fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing Thoughts
&lt;/h2&gt;

&lt;p&gt;Incidents like this one don't make the highlight. There's no dramatic zero-day, no sophisticated exploit, no novel attack vector. It's a script, a proxy network, and an endpoint that was never designed with abuse in mind. And that's what makes it so easy to overlook.&lt;/p&gt;

&lt;p&gt;The technical fix here wasn't complex: a couple of WAF rules and a CAPTCHA integration. What required experience was recognising why the obvious defences were failing, making the uncomfortable call to block legitimate users to stop the bleeding, and thinking through the layered approach that would hold up beyond this specific attack.&lt;/p&gt;

&lt;p&gt;Security exploits are only getting more sophisticated and more frequent. As you build, make it a habit to ask not just how the feature is meant to be used, but how it can be abused.&lt;/p&gt;

&lt;p&gt;If you're building anything with public-facing endpoints that trigger emails or external services, treat abuse protection as a first-class requirement, not an afterthought. &lt;em&gt;Your sender reputation, your cloud bill, and your users will thank you for it.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Thank You for Reading
&lt;/h2&gt;

&lt;p&gt;You can follow me on &lt;a href="https://www.linkedin.com/in/alhazan-mubarak/" rel="noopener noreferrer"&gt;&lt;strong&gt;LinkedIn&lt;/strong&gt;&lt;/a&gt; and subscribe to my &lt;a href="https://www.youtube.com/@poly4" rel="noopener noreferrer"&gt;&lt;strong&gt;YouTube Channel&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt;,&lt;/strong&gt; where I share more valuable content. Also, let me know your thoughts in the comments section.&lt;/p&gt;

</description>
      <category>waf</category>
      <category>captcha</category>
    </item>
    <item>
      <title>Coordinar deploys de frontend y backend sin orquestado, usando Github Actions</title>
      <dc:creator>Franchesco Romero</dc:creator>
      <pubDate>Thu, 28 May 2026 16:52:34 +0000</pubDate>
      <link>https://dev.to/aws-builders/coordinar-deploys-de-frontend-y-backend-sin-orquestado-usando-github-actions-4fp5</link>
      <guid>https://dev.to/aws-builders/coordinar-deploys-de-frontend-y-backend-sin-orquestado-usando-github-actions-4fp5</guid>
      <description>&lt;h2&gt;
  
  
  El Setup
&lt;/h2&gt;

&lt;p&gt;Un setup chiquito de SPA + API donde dos workflows de GitHub Actions&lt;br&gt;
salen en paralelo en cada push a &lt;code&gt;main&lt;/code&gt;. Probablemente un setup que no usaría en prod, pero algo que si uso para mis proyectos personales.&lt;/p&gt;

&lt;p&gt;Ahora bien esto trae un problema de coordinación (el frontend llega a los usuarios antes de que exista el endpoint de la API), cuatro opciones, y el gate de ~80 líneas de bash que fue el ganador para nuestro caso de uso&lt;/p&gt;

&lt;p&gt;El codebase: una SPA de React en CloudFront + S3, un backend FastAPI en AWS ECS Fargate, infraestructura en CDK. &lt;br&gt;
Dos workflows (&lt;code&gt;deploy-frontend.yml&lt;/code&gt;, &lt;code&gt;deploy-backend.yml&lt;/code&gt;) disparados por push a &lt;code&gt;main&lt;/code&gt; con path filters.&lt;/p&gt;

&lt;p&gt;Acá el código para seguir el post paso a paso:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/elchesco" rel="noopener noreferrer"&gt;
        elchesco
      &lt;/a&gt; / &lt;a href="https://github.com/elchesco/github-actions-combined-deploy-with-no-orchestrator" rel="noopener noreferrer"&gt;
        github-actions-combined-deploy-with-no-orchestrator
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Coordinating frontend and backend deploys without an orchestrator:
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Code companion — frontend/backend deploy coordination post&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Each folder maps to one stage of the post's narrative:&lt;/p&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;00-independent/         starting point — two workflows, no gate, the race exists
01-coupled-detection/   git diff vs the parent commit to know if backend changed
02-poll-by-sha/         GitHub Actions API filtered by head_sha
03-grace-window/        handle "backend run not registered yet" (race fix)
04-final/               full step with meaningful error surfacing
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Suggested reading order&lt;/h2&gt;
&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;00-independent/&lt;/code&gt;&lt;/strong&gt; — the baseline; what most projects start with.&lt;/li&gt;
&lt;li&gt;Each stage folder in order — the post's evolution.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;04-final/&lt;/code&gt;&lt;/strong&gt; — drop this into your &lt;code&gt;deploy-frontend.yml&lt;/code&gt; as
the first step inside &lt;code&gt;build-and-deploy&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Notes&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;00-independent/&lt;/code&gt; is two separate files — they exist as &lt;code&gt;.yml&lt;/code&gt; so
you can diff them against your own workflows.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;04-final/&lt;/code&gt; contains the complete step block plus the workflow
permissions and checkout config it depends on. Copy all three
pieces or the gate will silently fail open.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;actions: read&lt;/code&gt; permission is…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/elchesco/github-actions-combined-deploy-with-no-orchestrator" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;El approach simple aguanta mientras tu push esté dominado por&lt;br&gt;
cambios de un solo dominio. Deja de funcionar cuando la mayoría de los pushes tocan los dos o más.&lt;/p&gt;
&lt;h2&gt;
  
  
  El problema
&lt;/h2&gt;

&lt;p&gt;Dos workflows en la misma branch &lt;code&gt;main&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="c1"&gt;# deploy-frontend.yml&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;paths&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;frontend/**"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# deploy-backend.yml&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;paths&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;backend/**"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;infra/**"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Un push que toca los dos — digamos, "agrega el endpoint&lt;br&gt;
&lt;code&gt;/api/v1/leaderboard&lt;/code&gt; y la UI que lo llama" — dispara ambos workflows&lt;br&gt;
en paralelo. Los builds de frontend normalmente son más rápidos (sin&lt;br&gt;
Docker, sin rollout de ECS). Así que un usuario que refresca entre el&lt;br&gt;
minuto 2 (SPA subida) y el minuto 6 (backend sano en ECS) pega contra&lt;br&gt;
una SPA nuevecita apuntando a un endpoint que todavía no existe. La&lt;br&gt;
consola del browser muestra un 404. Sentry pega un brinco, se disparan alertas de Cloudwatch, el usuario recarga, pega contra el caché, y ve el mismo error.&lt;/p&gt;

&lt;p&gt;Difícil de cachar en &lt;code&gt;dev&lt;/code&gt; porque el stack local arranca los dos servicios juntos. Fácil de cachar en prod una vez que pasa.&lt;/p&gt;
&lt;h2&gt;
  
  
  Las opciones
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Opción 1: workflows paralelos independientes (el baseline de no hacer nada)
&lt;/h3&gt;

&lt;p&gt;Con lo que arrancamos. Cada workflow escucha sus propios paths y&lt;br&gt;
despliega en su propio tiempo. Cero coordinación.&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;# ambos workflows&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;...&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pros: &lt;br&gt;
cero setup, deploys de un solo dominio lo más rápido posible,&lt;br&gt;
aislamiento total. &lt;br&gt;
Contras: &lt;br&gt;
la ventana de race de arriba. El costo solo aparece en los pushes acoplados.&lt;/p&gt;
&lt;h3&gt;
  
  
  Opción 2: colapsar en un solo workflow con orden explícito
&lt;/h3&gt;

&lt;p&gt;El fix más "obvio": escribir un &lt;code&gt;deploy.yml&lt;/code&gt; con &lt;code&gt;deploy-backend&lt;/code&gt; como &lt;code&gt;job 1&lt;/code&gt; y &lt;code&gt;deploy-frontend&lt;/code&gt; con &lt;code&gt;needs: [deploy-backend]&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;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;deploy-backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;self-hosted&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;...&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;deploy-frontend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;deploy-backend&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;self-hosted&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;...&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pros: &lt;br&gt;
modelo mental trivial; GitHub Actions maneja el orden. &lt;br&gt;
Contras:&lt;br&gt;
un push de solo-frontend ahora espera por un backend que no cambió (o&lt;br&gt;
necesita un &lt;code&gt;if:&lt;/code&gt; explícito para saltárselo, que es su propia&lt;br&gt;
complejidad). Un step de lint flaky en el backend bloquea el deploy de frontend que ni siquiera dependía de los cambios del backend. Perdimos la independencia de path filters que hacía deseables los workflows paralelos para empezar.&lt;/p&gt;
&lt;h3&gt;
  
  
  Opción 3: el frontend &lt;em&gt;gatea&lt;/em&gt; contra el SHA del backend (el gate simple)
&lt;/h3&gt;

&lt;p&gt;Conservar los dos workflows. Agregar un step al inicio del workflow de frontend: &lt;em&gt;si este commit también tocó backend, espera a que el&lt;br&gt;
workflow de backend en el mismo SHA termine bien&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Pros: &lt;br&gt;
cero overhead en pushes desacoplados (el caso común); se conserva&lt;br&gt;
el paralelismo del caso común; el gate son ~80 líneas de bash + un&lt;br&gt;
&lt;code&gt;curl&lt;/code&gt; a la API de GitHub Actions. Sin infra nueva, sin servicio&lt;br&gt;
orquestador, sin artifact pinning.&lt;/p&gt;

&lt;p&gt;Contras: &lt;br&gt;
bash. Polling. El gate corre en el runner del frontend, así&lt;br&gt;
que cuesta minutos de runner mientras espera (gratis en self-hosted,&lt;br&gt;
facturable en &lt;code&gt;ubuntu-latest&lt;/code&gt;).&lt;/p&gt;
&lt;h3&gt;
  
  
  Opción 4: pin del frontend a un artifact buildeado del backend
&lt;/h3&gt;

&lt;p&gt;La respuesta correcta de principio: cada build de frontend embebe la&lt;br&gt;
versión de backend contra la que se construyó; la SPA se niega a llamar una API que no haga match.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// frontend&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;REQUIRED_API_VERSION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2026.05.27.a&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// inyectado en el build&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;apiHealthcheck&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;REQUIRED_API_VERSION&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;showStaleBanner&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pros: &lt;br&gt;
cero ventana de race incluso con rollouts totalmente independientes; el cliente puede caer a un banner de "refresca para actualizar"; funciona durante rollbacks. &lt;br&gt;
Contras: &lt;br&gt;
cada cambio de endpoint se vuelve un contrato versionado; necesitas un endpoint de discovery &lt;code&gt;/api/version&lt;/code&gt; y lógica en la SPA para manejar el mismatch; coordinar across clientes móviles eventualmente cuesta más que el gate.&lt;/p&gt;
&lt;h3&gt;
  
  
  Cómo elegir
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Patrón de push&lt;/th&gt;
&lt;th&gt;Mejor opción&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Pushes mayormente de un solo dominio&lt;/td&gt;
&lt;td&gt;Opción 3 (gate)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pushes mayormente acoplados&lt;/td&gt;
&lt;td&gt;Opción 2 (un solo workflow)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clientes de larga vida / móvil / offline / una app de escritorio que no puedes forzar a refrescar&lt;/td&gt;
&lt;td&gt;Opción 4 (contrato versionado)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Nuestra distribución: ~80% solo-backend, ~15% solo-frontend, ~5%&lt;br&gt;
acoplado. La Opción 3 fue el match obvio. El resto del post es su&lt;br&gt;
evolución.&lt;/p&gt;
&lt;h2&gt;
  
  
  Etapa 0: el punto de partida
&lt;/h2&gt;

&lt;p&gt;Dos workflows, sin coordinación:&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;# .github/workflows/deploy-frontend.yml&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;Deploy Frontend&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;paths&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;frontend/**"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.github/workflows/deploy-frontend.yml"&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build-and-deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;self-hosted&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v5&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pnpm install --frozen-lockfile &amp;amp;&amp;amp; pnpm build&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws s3 sync dist s3://myapp-frontend&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws cloudfront create-invalidation --distribution-id $DIST_ID --paths '/*'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Push acoplado → race → 404s en producción. &lt;br&gt;
Fix: &lt;em&gt;gatear&lt;/em&gt; el deploy de frontend contra el de backend cuando el commit cambió código de backend.&lt;/p&gt;
&lt;h2&gt;
  
  
  Etapa 1: detectar el push acoplado
&lt;/h2&gt;

&lt;p&gt;Antes de hacer nada más, el gate tiene que responder: &lt;em&gt;¿este commit de verdad tocó el backend?&lt;/em&gt; Si no, no esperamos, procede de inmediato y no desperdicies un minuto de runner.&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="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v5&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Necesitamos ≥ 2 commits para diffear contra el padre.&lt;/span&gt;
    &lt;span class="na"&gt;fetch-depth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&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;Detect coupled push&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;set -euo pipefail&lt;/span&gt;
    &lt;span class="s"&gt;changed=$(git diff --name-only HEAD~1 HEAD 2&amp;gt;/dev/null || true)&lt;/span&gt;
    &lt;span class="s"&gt;if ! echo "$changed" | grep -qE "^(backend/|infra/|\.github/workflows/deploy-backend\.yml$)"; then&lt;/span&gt;
      &lt;span class="s"&gt;echo "No backend/infra changes — proceeding."&lt;/span&gt;
      &lt;span class="s"&gt;exit 0&lt;/span&gt;
    &lt;span class="s"&gt;fi&lt;/span&gt;
    &lt;span class="s"&gt;echo "Backend changes detected — gate engaged."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El patrón de paths espeja el bloque &lt;code&gt;paths:&lt;/code&gt; de &lt;code&gt;deploy-backend.yml&lt;/code&gt;&lt;br&gt;
exactito — si un path dispara el workflow de backend, el gate tiene que esperarlo. La diferencia entre los dos es la causa #1 de false&lt;br&gt;
proceeds, así que mantenlos pegaditos en el code review.&lt;/p&gt;

&lt;p&gt;El &lt;code&gt;fetch-depth: 2&lt;/code&gt; es la trampa — &lt;code&gt;actions/checkout@v5&lt;/code&gt; viene por&lt;br&gt;
default en shallow &lt;code&gt;1&lt;/code&gt;, y &lt;code&gt;git diff HEAD~1&lt;/code&gt; en un checkout de depth-1&lt;br&gt;
regresa nada en silencio, lo cual el script lee como "no hay cambios de backend — procede". (Pegamos contra esto en el primer deploy después de &lt;em&gt;shipear&lt;/em&gt; el gate. Cáchalo con un guard, no nomás con documentación.)&lt;/p&gt;
&lt;h2&gt;
  
  
  Etapa 2: hacer poll al run del backend por SHA
&lt;/h2&gt;

&lt;p&gt;Ahora sabemos que hay que esperar. El mecanismo es la REST API de&lt;br&gt;
GitHub Actions filtrada por &lt;code&gt;head_sha&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;&lt;span class="nv"&gt;api&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://api.github.com/repos/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GITHUB_REPOSITORY&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/actions/workflows/deploy-backend.yml/runs"&lt;/span&gt;
&lt;span class="nv"&gt;query&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"head_sha=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GITHUB_SHA&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;per_page=1"&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;i &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;seq &lt;/span&gt;1 160&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;  &lt;span class="c"&gt;# techo de 40 min a 15s/poll&lt;/span&gt;
  &lt;span class="nv"&gt;payload&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-sS&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GH_TOKEN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Accept: application/vnd.github+json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;api&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;?&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;query&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$payload&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.workflow_runs[0].status // empty'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="nv"&gt;conclusion&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$payload&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.workflow_runs[0].conclusion // empty'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$status&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"completed"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    case&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$conclusion&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in
      &lt;/span&gt;success|skipped&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;0 &lt;span class="p"&gt;;;&lt;/span&gt;
      &lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"::error::Backend &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;conclusion&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1 &lt;span class="p"&gt;;;&lt;/span&gt;
    &lt;span class="k"&gt;esac&lt;/span&gt;
  &lt;span class="k"&gt;fi
  &lt;/span&gt;&lt;span class="nb"&gt;sleep &lt;/span&gt;15
&lt;span class="k"&gt;done
&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"::error::Timed out waiting for backend deploy."&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El filtro &lt;code&gt;head_sha&lt;/code&gt; es el eje de todo — regresa el run de &lt;em&gt;este commit exacto&lt;/em&gt;, no "el último run en &lt;code&gt;main&lt;/code&gt;", que haría race con un push de fast-follow.&lt;/p&gt;

&lt;p&gt;El permiso &lt;code&gt;actions: read&lt;/code&gt; se tiene que agregar al bloque &lt;code&gt;permissions:&lt;/code&gt; del workflow — sin eso la API regresa 403 y el gate&lt;br&gt;
falla open en silencio.&lt;/p&gt;
&lt;h2&gt;
  
  
  Etapa 3: manejar el "todavía no se registra"
&lt;/h2&gt;

&lt;p&gt;El primer run en prod reveló un race: el workflow de frontend puede&lt;br&gt;
arrancar antes de que GitHub haya registrado el run del workflow de&lt;br&gt;
backend en el mismo SHA. La API regresa &lt;code&gt;total_count: 0&lt;/code&gt;, el script lee "no hay run de backend en este SHA, procede", y ya volvimos al problema original del 404.&lt;/p&gt;

&lt;p&gt;Fix: una grace window. Dale a GitHub hasta 30s para registrar el run.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Grace window — espera hasta 30s a que aparezca el run de backend.&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;i &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;seq &lt;/span&gt;1 6&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nv"&gt;count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl ... | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.total_count'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$count&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;"0"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;break
  sleep &lt;/span&gt;5
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Los 30s son empíricos — medí unos cuantos pushes acoplados, el delay de registro siempre fue &amp;lt; 10s pero brincó a 18s una vez durante un&lt;br&gt;
incidente de GitHub. 30s es lo suficientemente generoso como para que&lt;br&gt;
no hagamos false-proceed; el timeout exterior de 40 minutos absorbe el costo.&lt;/p&gt;

&lt;p&gt;Si después de 30s el run sigue sin existir, el script &lt;em&gt;sí&lt;/em&gt; cae a "no&lt;br&gt;
hay run en este SHA — procede". Esa es la decisión correcta: o el&lt;br&gt;
workflow de backend no se disparó (el path filter excluyó los cambios), o GitHub está tan degradado que un deploy de frontend es el menor de nuestros problemas. No le busques tres pies al gato.&lt;/p&gt;
&lt;h2&gt;
  
  
  Etapa 4: superficies de error con sentido
&lt;/h2&gt;

&lt;p&gt;Dos modos de falla necesitan manejo explícito para que el dev que ve el build en rojo pueda actuar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. La API de GitHub regresa 4xx (auth, rate limit, etc.).&lt;/span&gt;
fetch_runs&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;response status body
  &lt;span class="nv"&gt;response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-sS&lt;/span&gt; &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;%{http_code}"&lt;/span&gt; ...&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-n1&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'$d'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$status&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;"200"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"::error::GitHub API returned &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
    &lt;span class="k"&gt;return &lt;/span&gt;1
  &lt;span class="k"&gt;fi
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$body&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;curl -f&lt;/code&gt; sale con 22 sin body. El wrapper conserva el body para que el log de error diga "403 Forbidden: actions read permission missing" en lugar de "exit code 22, suerte".&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 2. El deploy de backend falló — saca la conclusion tal cual.&lt;/span&gt;
&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"::error::Backend deploy &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;conclusion&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. Abortando el deploy de frontend para que la SPA nunca apunte a una API ausente."&lt;/span&gt; &lt;span class="p"&gt;;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Los casos &lt;code&gt;failure|cancelled|timed_out&lt;/code&gt; todos colapsan a la misma&lt;br&gt;
acción (no deployar el frontend), pero imprimir la conclusion exacta te ahorra un click hacia el run del workflow de backend cuando estás&lt;br&gt;
investigando.&lt;/p&gt;

&lt;h2&gt;
  
  
  El resultado
&lt;/h2&gt;

&lt;p&gt;Un setup de dos workflows que:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cuesta &lt;strong&gt;0 segundos&lt;/strong&gt; en pushes desacoplados (el caso del 80%)&lt;/li&gt;
&lt;li&gt;Cuesta &lt;strong&gt;a lo mucho el wall time del deploy de backend&lt;/strong&gt; en pushes
acoplados&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Falla closed&lt;/strong&gt; — si el deploy de backend falla, el frontend no
shipea (sin tormenta de 404s)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Falla closed en el gate mismo&lt;/strong&gt; — mala respuesta de API, timeout,
permiso caído, todos salen con 1&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Desde que lo shipeamos (medido sobre seis semanas):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;47 pushes acoplados deployados limpio&lt;/li&gt;
&lt;li&gt;3 pushes acoplados donde el gate cachó un deploy de backend fallido
antes de que el frontend saliera (habría sido un outage visible para
el usuario)&lt;/li&gt;
&lt;li&gt;0 casos de gate haciendo false-proceed&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Lo que NO ayudó
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Intentar detectar "cambios de endpoint de API" desde el diff. Un cambio en un campo de schema de Pydantic basta), y los false negatives aquí son peores que los false positives. El check de path por git-diff es suficientemente bueno.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cancel-in-progress en el workflow de frontend.&lt;/strong&gt; Se ve atractivo matar el deploy de frontend en vuelo si llega un push nuevo — pero el cancel pasa a media S3-sync, dejando el bundle a medio subir. Combinado con un caché stale de CloudFront esto es &lt;em&gt;peor&lt;/em&gt; que el race condition original. Lo dejamos en &lt;code&gt;cancel-in-progress: false&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Qué sí ayudaría a futuro
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Mover la lógica del gate a un action reutilizable
(&lt;code&gt;actions/wait-for-workflow@v1&lt;/code&gt;). Las ~80 líneas de bash funcionan
pero están medio copy-pasteadas entre proyectos. &lt;/li&gt;
&lt;li&gt;Sacar el conteo de pushes acoplados a un dashboard.** Si el ratio se va del 5% hacia el 30%, el gate simple deja de ser la
herramienta correcta.&lt;/li&gt;
&lt;li&gt;Hacer el deploy de backend más rápido para que la espera sea más
corta cuando sí se active.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Lecciones
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Los path filters definen qué cosas tus workflows acuerdan que los
disparan. Mantenlos juntos y revísalos juntos.&lt;/li&gt;
&lt;li&gt;El filtro &lt;code&gt;head_sha&lt;/code&gt; en la API de Actions es lo más útil de todo
esto. Existe, es estable, está documentado, y convierte "¿en cuál run estoy esperando?" de un problema difícil a un solo query.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>githubactions</category>
      <category>ci</category>
    </item>
  </channel>
</rss>
