<?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: Davide De Sio</title>
    <description>The latest articles on DEV Community by Davide De Sio (@ddesio).</description>
    <link>https://dev.to/ddesio</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1402618%2F7b0b959e-0a8c-43b6-ad1d-90ae70727bb6.png</url>
      <title>DEV Community: Davide De Sio</title>
      <link>https://dev.to/ddesio</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ddesio"/>
    <language>en</language>
    <item>
      <title>Building KiroGraph: a 100% local semantic code knowledge graph for Kiro</title>
      <dc:creator>Davide De Sio</dc:creator>
      <pubDate>Wed, 08 Apr 2026 14:59:28 +0000</pubDate>
      <link>https://dev.to/aws-builders/building-kirograph-a-100-local-semantic-code-knowledge-graph-for-kiro-2ja4</link>
      <guid>https://dev.to/aws-builders/building-kirograph-a-100-local-semantic-code-knowledge-graph-for-kiro-2ja4</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This is part of my "Build in Public" with Kiro series. I'm an AWS Community Builder, and this is the story of building a tool by using the tool itself, which is either very meta or very efficient, depending on how you look at it.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  🏃 TL;DR
&lt;/h2&gt;

&lt;p&gt;KiroGraph is a local code indexing system for &lt;a href="https://kiro.dev/" rel="noopener noreferrer"&gt;Kiro&lt;/a&gt; AI IDE. It turns your codebase into a queryable structural using a semantic graph, dramatically reducing AI tool calls and token usage (up to 90%).&lt;/p&gt;

&lt;p&gt;Instead of re-reading files with grep/glob, the AI queries a pre-built AST-based graph (plus optional embeddings), making code navigation faster, cheaper, and more scalable.&lt;/p&gt;

&lt;p&gt;It supports multiple vector engines (e.g. SQlite, PGlite, Orama Qdrant, Typesense) and is fully local, with an interactive installer and automatic syncing.&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/davide-desio-eleva" rel="noopener noreferrer"&gt;
        davide-desio-eleva
      &lt;/a&gt; / &lt;a href="https://github.com/davide-desio-eleva/kirograph" rel="noopener noreferrer"&gt;
        kirograph
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Semantic code knowledge graph for Kiro: fewer tool calls, instant symbol lookups, 100% local.
    &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;KiroGraph&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/davide-desio-eleva/kirograph/assets/kirograph.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fdavide-desio-eleva%2Fkirograph%2FHEAD%2Fassets%2Fkirograph.png" alt="KiroGraph terminal"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Semantic code knowledge graph for &lt;a href="https://kiro.dev" rel="nofollow noopener noreferrer"&gt;Kiro&lt;/a&gt;: fewer tool calls, instant symbol lookups, 100% local.&lt;/p&gt;
&lt;p&gt;Inspired by &lt;a href="https://github.com/colbymchenry/codegraph" rel="noopener noreferrer"&gt;CodeGraph&lt;/a&gt; by &lt;a href="https://github.com/colbymchenry" rel="noopener noreferrer"&gt;colbymchenry&lt;/a&gt; for Claude Code, rebuilt natively for Kiro's MCP and hooks system.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Why KiroGraph?&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;When you ask Kiro to work on a complex task, it explores your codebase using file reads, grep, and glob searches. Every one of those is a tool call, and tool calls consume context and slow things down.&lt;/p&gt;
&lt;p&gt;KiroGraph gives Kiro a semantic knowledge graph that's pre-indexed and always up to date. Instead of scanning files to understand your code, Kiro queries the graph instantly: symbol relationships, call graphs, type hierarchies, impact radius — all in a single MCP tool call.&lt;/p&gt;
&lt;p&gt;The result is fewer tool calls, less context used, and faster responses on complex tasks.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What Gets Indexed?&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;KiroGraph uses &lt;a href="https://tree-sitter.github.io/tree-sitter/" rel="nofollow noopener noreferrer"&gt;tree-sitter&lt;/a&gt; to parse your source files into an AST and extract:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nodes&lt;/strong&gt; — functions, methods…&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/davide-desio-eleva/kirograph" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  🔭 How it started
&lt;/h2&gt;

&lt;p&gt;A few weeks ago I came across &lt;a href="https://github.com/colbymchenry/codegraph" rel="noopener noreferrer"&gt;CodeGraph&lt;/a&gt; by &lt;a href="https://github.com/colbymchenry" rel="noopener noreferrer"&gt;Colby McHenry&lt;/a&gt;, a semantic code knowledge graph for Claude Code. The idea was brilliant: instead of letting the AI wander through your codebase with grep and file reads, you give it a pre-indexed 100% local graph it can query instantly. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fewer tool calls, less context burned, faster responses.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I use &lt;a href="https://kiro.dev" rel="noopener noreferrer"&gt;Kiro&lt;/a&gt;, AWS's spec-driven AI IDE, and there was nothing equivalent for it. So I did what any reasonable developer does when they see a good idea: I ported it.&lt;/p&gt;

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

&lt;p&gt;That's how KiroGraph started but the community’s genuine interest motivated me to essentially rebuild CodeGraph from the ground up and significantly expand its functionality.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  💸 Why this should matters to us (the token problem)
&lt;/h2&gt;

&lt;p&gt;When you ask an AI agent to work on a complex task, "fix the auth bug", "add rate limiting to the API", "refactor the payment service", the agent needs to understand your codebase before it can do anything useful. The way it typically does that is by reading files, running grep, globbing directories. Every one of those is a tool call.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tool calls cost tokens&lt;/strong&gt;. Lots of them. And they're slow.&lt;br&gt;
Aside from cost, &lt;strong&gt;we’re currently in a period where reasonable plans for using AI are either being rate-limited or subject to increasingly restrictive limits&lt;/strong&gt;. The community is actively exploring solutions to optimize requests and responses, reduce token usage, and improve overall efficiency.&lt;/p&gt;

&lt;p&gt;The insight behind KiroGraph (and CodeGraph before it) is simple: &lt;strong&gt;your codebase doesn't change that often&lt;/strong&gt;. Between agent runs, you might touch a handful of files. &lt;/p&gt;

&lt;p&gt;Why should the agent re-discover the structure from scratch every single time?&lt;/p&gt;

&lt;p&gt;KiroGraph pre-indexes everything, functions, methods, classes, interfaces, types, call relationships, import graphs, type hierarchies, into a 100% local SQLite database. When Kiro needs context, it doesn't read files. It queries the graph. One MCP tool call instead of twenty file reads and multiple tool calls.&lt;/p&gt;

&lt;p&gt;The impact is real: tasks that once consumed entire context windows simply don’t anymore: saving tokens, improving efficiency, and delivering greater speed.&lt;/p&gt;
&lt;h2&gt;
  
  
  ⚖️ Benchmark
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;An image is worth a thousand words&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%2Fkt1o01bxal0vidnyaoht.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkt1o01bxal0vidnyaoht.png" alt=" " width="800" height="598"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Up to a 90%+ reduction in token usage for common read patterns in the KiroGraph codebase. I can confirm similar results across different and larger codebases as well.&lt;/p&gt;

&lt;p&gt;These numbers represent the average outcome of identical requests executed with and without KiroGraph, across different semantic engines for comparison.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What this practically means for me is that I used up all my tokens with Kiro on the $20 plan in just 10 days. Now, I’ve gone a full month without even reaching the full allowance.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  🏗️ KiroGraph architecture
&lt;/h2&gt;

&lt;p&gt;The tool has two indexing layers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Structural indexing&lt;/strong&gt; is always on. &lt;a href="https://tree-sitter.github.io/tree-sitter/" rel="noopener noreferrer"&gt;tree-sitter&lt;/a&gt; parses every source file into an AST and extracts nodes (functions, classes, routes, components, 24 kinds total) and edges (calls, imports, extends, implements, references, and more). Everything lands in &lt;code&gt;kirograph.db&lt;/code&gt;. This powers all the graph traversal tools: find callers, trace impact, detect circular deps, find dead code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Semantic indexing&lt;/strong&gt; is opt-in. When you enable it, KiroGraph generates 768-dimensional vector embeddings for every embeddable symbol using &lt;code&gt;nomic-ai/nomic-embed-text-v1.5&lt;/code&gt; (~130MB, downloaded once to &lt;code&gt;~/.kirograph/models/&lt;/code&gt;). This powers natural-language search, ask for "auth middleware" and get the relevant functions even if they're named &lt;code&gt;validateJwt&lt;/code&gt; or &lt;code&gt;checkPermissions&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The index stays fresh automatically via &lt;strong&gt;Kiro hooks&lt;/strong&gt;. &lt;br&gt;
File saved, mark dirty. Agent stops, sync if dirty. &lt;br&gt;
Batched, efficient, zero overhead during active editing.&lt;/p&gt;

&lt;p&gt;The agent knows what to do through a Kiro steering file, which the final KiroGraph adopter can easily adapt to suit their specific needs.&lt;/p&gt;
&lt;h2&gt;
  
  
  🧠 Is KiroGraph a RAG/GraphRAG?
&lt;/h2&gt;

&lt;p&gt;It’s useful to compare KiroGraph to both a classic RAG system and a GraphRAG approach, because it sits somewhere in between, but also slightly outside both categories.&lt;/p&gt;

&lt;p&gt;1) A local RAG works on unstructured text, splitting documents into chunks and retrieving the most relevant pieces via embeddings.&lt;br&gt;
&lt;strong&gt;KiroGraph instead indexes code structure&lt;/strong&gt;, where functions, classes, and relationships come directly from the AST.&lt;br&gt;
This removes chunking entirely and &lt;strong&gt;replaces text retrieval with symbol-level navigation over a code graph.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;2) GraphRAG builds graphs by extracting entities and relationships from documents, then uses them to improve retrieval quality.&lt;br&gt;
&lt;strong&gt;KiroGraph doesn’t infer the graph from text, it derives it deterministically from the codebase structure itself.&lt;/strong&gt;&lt;br&gt;
As a result, its graph is not an approximation of knowledge, but a direct representation of the system architecture.&lt;/p&gt;

&lt;p&gt;The key difference: RAG retrieves text, GraphRAG organizes text, KiroGraph represents code structure.&lt;br&gt;
And embeddings in KiroGraph are optional, not foundational.&lt;br&gt;
The core idea is not better retrieval, but queryable program structure with semantic enrichment on top.&lt;/p&gt;

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

&lt;p&gt;Here's where it got interesting. &lt;br&gt;
Once you have embeddings, you need somewhere to put them, and &lt;em&gt;how&lt;/em&gt; you store and search them has real consequences at scale.&lt;/p&gt;

&lt;p&gt;The original approach is &lt;strong&gt;cosine similarity over all vectors in SQLite&lt;/strong&gt;. That's fine for small to medium projects, but for a large codebase with thousands of indexed symbols, you want approximate nearest-neighbour (ANN) search with a proper index structure.&lt;/p&gt;

&lt;p&gt;So I built support for seven engines, each solving a slightly different problem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;cosine (default): the original linear scan over the &lt;code&gt;vectors&lt;/code&gt; table in &lt;code&gt;kirograph.db&lt;/code&gt;. No extra deps. Works great up to a few thousand symbols. If you just want to try semantic search without any setup, this is it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/asg017/sqlite-vec" rel="noopener noreferrer"&gt;Alex Garcia's sqlite-vec&lt;/a&gt; brings ANN search to SQLite via a native extension. Sub-linear query time, stays in the SQLite ecosystem. Best for large codebases that don't want to run a separate process.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/oramasearch/orama" rel="noopener noreferrer"&gt;Orama&lt;/a&gt; does something clever: hybrid search. One query combines full-text relevance and vector similarity, which produces better results than running them separately and merging. Pure JS, no native compilation. If you want the best result quality and no native dependencies, Orama is the choice.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/electric-sql/pglite" rel="noopener noreferrer"&gt;PGlite&lt;/a&gt; is PostgreSQL compiled to WASM with the &lt;code&gt;pgvector&lt;/code&gt; extension. You get &lt;strong&gt;exact&lt;/strong&gt; (not approximate) nearest-neighbour search, &lt;code&gt;ON CONFLICT&lt;/code&gt; upserts, HNSW indexing, all the PostgreSQL semantics, in-process, no server. Pure WASM means no native binaries and no compilation. And because it's exact, results are deterministic and reproducible. &lt;strong&gt;I particularly like this one.&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/lancedb/lancedb" rel="noopener noreferrer"&gt;LanceDB&lt;/a&gt; stores embeddings in Apache Lance columnar format. Columnar storage is efficient for batch reads and writes, which matters a lot during indexing. Pure JS, no native deps, sub-linear ANN search.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/qdrant/qdrant" rel="noopener noreferrer"&gt;Qdrant&lt;/a&gt; is a dedicated vector database, HNSW index, cosine distance, the full feature set. KiroGraph spawns the Qdrant binary as a managed child process via &lt;code&gt;qdrant-local&lt;/code&gt;. The server runs as a persistent background daemon, state tracked in &lt;code&gt;.kirograph/qdrant-server.json&lt;/code&gt;. &lt;strong&gt;This is the heavy option.&lt;/strong&gt; You get Qdrant's full query capabilities and a proper production-grade vector store. The trade-off is you're now running a binary.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/typesense/typesense" rel="noopener noreferrer"&gt;Typesense&lt;/a&gt; is a search engine that added HNSW vector search. KiroGraph auto-downloads the binary (~37MB, cached at &lt;code&gt;~/.kirograph/bin/&lt;/code&gt;) and manages it as a background daemon. State tracked in &lt;code&gt;.kirograph/typesense-server.json&lt;/code&gt;. Similar to Qdrant in concept, persistent binary daemon, but with Typesense's search-engine heritage. Very excellent for hybrid queries.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  🛠️ Adding installer because DevEx always matters
&lt;/h2&gt;

&lt;p&gt;A lot of CLI tools treat configuration as an afterthought. You edit a config file, restart, wonder why nothing worked, read the docs, try again. &lt;strong&gt;It's friction.&lt;/strong&gt; You don't want friction when working within an AI powered IDE like Kiro.&lt;/p&gt;

&lt;p&gt;I wanted KiroGraph's setup to be genuinely good and simple. &lt;/p&gt;

&lt;p&gt;So the installer is interactive, not just &lt;code&gt;yes/no&lt;/code&gt; prompts but arrow-key menus with descriptions for each option. Run it once and you walk away with a fully working setup, no post-install surprises.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kirograph &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


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

&lt;p&gt;Here's every decision the installer walks you through, and why each one matters.&lt;/p&gt;
&lt;h3&gt;
  
  
  Enable semantic embeddings
&lt;/h3&gt;

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

&lt;p&gt;The first question is whether to turn on semantic search at all. Structural indexing is always on and costs nothing extra. Semantic indexing is opt-in because it requires a local embedding model (~130MB, downloaded once) and adds time to every index run.&lt;/p&gt;

&lt;p&gt;If your team mostly does exact symbol lookups, "go to definition" style queries, structural-only is fast and lightweight. If you want to ask things like "where is rate limiting handled?" or "which functions deal with user authentication?", you need embeddings.&lt;/p&gt;

&lt;p&gt;The installer is upfront about this: it tells you what you're signing up for before you say yes.&lt;/p&gt;
&lt;h3&gt;
  
  
  Embedding model
&lt;/h3&gt;

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

&lt;p&gt;Once you enable embeddings, the installer asks which HuggingFace model to use. The default is &lt;code&gt;nomic-ai/nomic-embed-text-v1.5&lt;/code&gt;, a solid general-purpose model that produces 768-dimensional embeddings and runs well locally via Ollama.&lt;/p&gt;

&lt;p&gt;You can enter any HuggingFace model identifier in &lt;code&gt;org/model-name&lt;/code&gt; format. If you enter something non-standard, the installer rejects it and explains the expected format. If you enter a non-default model, it reminds you to run &lt;code&gt;ollama pull &amp;lt;model&amp;gt;&lt;/code&gt; before indexing.&lt;/p&gt;

&lt;p&gt;The reason this is configurable: &lt;strong&gt;embedding quality varies by domain.&lt;/strong&gt; Code-specific models like &lt;code&gt;jinaai/jina-embeddings-v2-base-code&lt;/code&gt; may outperform general models on certain queries. &lt;br&gt;
&lt;strong&gt;Giving you control here means you're not locked in.&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Semantic engine
&lt;/h3&gt;

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

&lt;p&gt;This is the arrow-key menu. Each option shows a one-line description so you can make an informed choice without reading docs:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;? Choose the semantic search engine:
  ❯ cosine      In-process cosine similarity. No extra deps. Best for small/medium projects.
    sqlite-vec  ANN index. Sub-linear search. Best for large codebases. Needs: better-sqlite3, sqlite-vec (native).
    orama       Hybrid search (full-text + vector). Pure JS. Needs: @orama/orama, ...
    pglite      Hybrid search via PostgreSQL + pgvector. Exact results. Pure WASM. Needs: @electric-sql/pglite.
    lancedb     ANN search via LanceDB (Apache Lance columnar format). Pure JS. Needs: @lancedb/lancedb.
    qdrant      ANN search via Qdrant embedded binary (HNSW index, Cosine). Needs: qdrant-local.
    typesense   ANN search via Typesense (auto-downloaded binary, HNSW, Cosine). Needs: typesense.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;After you pick one, the installer immediately runs &lt;code&gt;npm install&lt;/code&gt; for the required dependencies. &lt;strong&gt;No separate step, no forgotten follow-up. If the install fails, it tells you exactly what to run manually.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For engines that spawn a binary (qdrant, typesense), it also asks whether you want a dashboard (a web UI to navigate the vectors).&lt;/p&gt;

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

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

&lt;p&gt;This controls whether KiroGraph reads JSDoc, Python docstrings, and inline comments from your source files and stores them as symbol metadata. Enabled by default.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Docstrings significantly improve semantic search quality.&lt;/strong&gt; When a function has a good docstring, the embedding captures its &lt;em&gt;intent&lt;/em&gt;, not just its name. A function called &lt;code&gt;proc&lt;/code&gt; with a docstring "processes incoming webhook payloads and dispatches to handlers" will surface correctly when someone searches for "webhook processing". Without the docstring, the name alone gives the model almost nothing to work with.&lt;/p&gt;

&lt;p&gt;The trade-off is slightly longer indexing time. For most projects it's negligible. The installer lets you disable it if you're indexing a very large codebase and want the fastest possible first run.&lt;/p&gt;
&lt;h3&gt;
  
  
  Track call sites
&lt;/h3&gt;

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

&lt;p&gt;This controls whether KiroGraph records the exact line and column of every function call when building call graph edges. Enabled by default.&lt;/p&gt;

&lt;p&gt;With call sites tracked, you get precise "go to call site" information in addition to "which functions call this symbol". The &lt;code&gt;kirograph_callers&lt;/code&gt; and &lt;code&gt;kirograph_callees&lt;/code&gt; MCP tools return not just the caller's name and file, but the exact location of the call. This is what makes the call graph actually useful for debugging and impact analysis.&lt;/p&gt;

&lt;p&gt;The trade-off is index size: call site data adds rows to the edges table. On codebases with millions of call expressions this can get large. If you only care about the structural shape of the call graph and not the precise locations, you can disable this to keep the database lean.&lt;/p&gt;
&lt;h3&gt;
  
  
  Indexing
&lt;/h3&gt;

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

&lt;p&gt;The final prompt asks whether to run the first full index immediately. If you say yes, the installer runs &lt;code&gt;kirograph index&lt;/code&gt;, shows you a live progress output (files scanned, symbols extracted, embeddings generated), and reports the final counts.&lt;/p&gt;

&lt;p&gt;This is the "zero to working" moment: by the time the installer exits, your graph is built and Kiro can start using it. No deferred setup, no "remember to run this before you start".&lt;/p&gt;

&lt;p&gt;Each phase is surfaced in the output so it's always clear what's happening: scanning files, parsing, resolving references, detecting languages and frameworks, generating embeddings. If something is slow, you know exactly where.&lt;/p&gt;

&lt;p&gt;The philosophy across all of this is the same: the installer should leave you in a working state and make every choice transparent. If something requires a separate binary or native compilation, you know before you commit to it. If a step can be done for you automatically, it is.&lt;/p&gt;
&lt;h2&gt;
  
  
  🔁 The feedback loop that accelerated everything
&lt;/h2&gt;

&lt;p&gt;Here's the part I didn't fully anticipate: because I've used KiroGraph to indexes itself, so I could use it in Kiro &lt;em&gt;while building it&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Every time I added a new engine, the Kiro agent could immediately use &lt;code&gt;kirograph_context&lt;/code&gt; and &lt;code&gt;kirograph_callers&lt;/code&gt; to understand the existing codebase structure. It knew which interfaces to implement, where the integration points were, what the existing patterns looked like, without me having to explain any of it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is augmenting spec-driven development with actual graph-powered context.&lt;/strong&gt; The agent writes specs and code that fits the existing architecture because it can see the architecture. Not by reading files, by querying the graph.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The speed difference and saving in tokens is hard to overstate&lt;/strong&gt;. Tasks that would have required multiple rounds of "read this file, now read that file, now understand how they connect" collapsed into a single &lt;code&gt;kirograph_context&lt;/code&gt; call followed by implementation.&lt;/p&gt;
&lt;h2&gt;
  
  
  📊 Adding some dashboard
&lt;/h2&gt;

&lt;p&gt;Once you have Qdrant and Typesense running as daemons, my idea was also to have visibility into what's actually in the vector store as both have community open sourced web UIs.&lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;Qdrant&lt;/strong&gt;: the &lt;a href="https://github.com/qdrant/qdrant-web-ui" rel="noopener noreferrer"&gt;Qdrant Web UI&lt;/a&gt; is served &lt;em&gt;by Qdrant itself&lt;/em&gt;. KiroGraph downloads the &lt;code&gt;dist-qdrant.zip&lt;/code&gt; release asset, extracts it with &lt;code&gt;unzip&lt;/code&gt;, caches it, and sets the env var before spawning the binary. The dashboard is then available at &lt;code&gt;http://127.0.0.1:&amp;lt;port&amp;gt;/dashboard&lt;/code&gt; natively.&lt;/p&gt;

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

&lt;p&gt;For &lt;strong&gt;Typesense&lt;/strong&gt;: &lt;a href="https://github.com/bfritscher/typesense-dashboard" rel="noopener noreferrer"&gt;bfritscher/typesense-dashboard&lt;/a&gt; is a static React app. KiroGraph downloads it from GitHub, caches it at &lt;code&gt;.kirograph/typesense/dashboard/&lt;/code&gt;, and serves it locally via a Node HTTP server.&lt;/p&gt;

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

&lt;p&gt;Both are unified under &lt;code&gt;kirograph dashboard start&lt;/code&gt; / &lt;code&gt;kirograph dashboard stop&lt;/code&gt;, the command reads &lt;code&gt;semanticEngine&lt;/code&gt; from config and dispatches to the right implementation.&lt;/p&gt;
&lt;h2&gt;
  
  
  🧩 Supported languages and frameworks
&lt;/h2&gt;

&lt;p&gt;As per &lt;a href="https://github.com/colbymchenry/codegraph" rel="noopener noreferrer"&gt;CodeGraph&lt;/a&gt; by &lt;a href="https://github.com/colbymchenry" rel="noopener noreferrer"&gt;Colby McHenry&lt;/a&gt;, KiroGraph supports different languages and framework (and it easy to add new ones).&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Language&lt;/th&gt;
&lt;th&gt;Framework&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;TypeScript&lt;/td&gt;
&lt;td&gt;React, Next.js, React Native, Svelte, SvelteKit, Express, Fastify, Koa&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JavaScript&lt;/td&gt;
&lt;td&gt;React, Next.js, React Native, Svelte, SvelteKit, Express, Fastify, Koa&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TSX / JSX&lt;/td&gt;
&lt;td&gt;Generic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;Django, Flask, FastAPI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Go&lt;/td&gt;
&lt;td&gt;Generic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rust&lt;/td&gt;
&lt;td&gt;Generic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Java&lt;/td&gt;
&lt;td&gt;Spring, Spring Boot, Spring MVC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C&lt;/td&gt;
&lt;td&gt;Generic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C++&lt;/td&gt;
&lt;td&gt;Generic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C#&lt;/td&gt;
&lt;td&gt;ASP.NET Core&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PHP&lt;/td&gt;
&lt;td&gt;Laravel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ruby&lt;/td&gt;
&lt;td&gt;Rails&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Swift&lt;/td&gt;
&lt;td&gt;SwiftUI, UIKit, Vapor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kotlin&lt;/td&gt;
&lt;td&gt;Generic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dart&lt;/td&gt;
&lt;td&gt;Generic&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2&gt;
  
  
  ➡️ What's next
&lt;/h2&gt;

&lt;p&gt;A few things on the roadmap:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;More engines.&lt;/strong&gt; The engine abstraction is clean, adding a new one means implementing four methods (&lt;code&gt;initialize&lt;/code&gt;, &lt;code&gt;upsert&lt;/code&gt;, &lt;code&gt;search&lt;/code&gt;, &lt;code&gt;count&lt;/code&gt;). &lt;a href="https://github.com/weaviate/weaviate" rel="noopener noreferrer"&gt;Weaviate&lt;/a&gt;, &lt;a href="https://github.com/chroma-core/chroma" rel="noopener noreferrer"&gt;Chroma&lt;/a&gt;, and &lt;a href="https://github.com/milvus-io/milvus" rel="noopener noreferrer"&gt;Milvus&lt;/a&gt; are very interesting candidates. I should evaluate if they fit the ecosystem and what they offer as peculiarity. Maybe a "plugin system" would be a good implementation to let folks implement their preferred semantic engine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;More languages and frameworks.&lt;/strong&gt; Also here a "plug'n'play" system to add new definition for languages and frameworks could be a good choice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Embed in a Kiro Power.&lt;/strong&gt; KiroGraph works with Kiro's hooks and steering, and is basically a CLI tool: a good choice could be to embed it into a configurable Kiro power to reduce the friction for folks who wants just install and vibe.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Smarter sync.&lt;/strong&gt; Currently, sync re-embeds every changed symbol. I’m considering introducing a content hash per embedding so we can skip unchanged symbols, even when the file has been modified.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cross-project search.&lt;/strong&gt; The graph is per-project right now. For monorepos or workspaces with shared libraries, cross-project symbol resolution would be genuinely useful.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Richer graph traversal.&lt;/strong&gt; &lt;code&gt;kirograph_path&lt;/code&gt; finds the shortest path between two symbols. I would love to add something like "explain this path", not just the nodes, but the semantic reason for each edge.&lt;/p&gt;
&lt;h2&gt;
  
  
  🚀 Just try it
&lt;/h2&gt;

&lt;p&gt;Go to &lt;a href="https://github.com/davide-desio-eleva/kirograph/stargazers" rel="noopener noreferrer"&gt;KiroGraph repository&lt;/a&gt;, fork it, try it. PR's are welcome.&lt;br&gt;
It’s not yet published on npm, so it should be considered an alpha version. Expect significant changes in the future so do not consider it stable.&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/davide-desio-eleva" rel="noopener noreferrer"&gt;
        davide-desio-eleva
      &lt;/a&gt; / &lt;a href="https://github.com/davide-desio-eleva/kirograph" rel="noopener noreferrer"&gt;
        kirograph
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Semantic code knowledge graph for Kiro: fewer tool calls, instant symbol lookups, 100% local.
    &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;KiroGraph&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/davide-desio-eleva/kirograph/assets/kirograph.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fdavide-desio-eleva%2Fkirograph%2FHEAD%2Fassets%2Fkirograph.png" alt="KiroGraph terminal"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Semantic code knowledge graph for &lt;a href="https://kiro.dev" rel="nofollow noopener noreferrer"&gt;Kiro&lt;/a&gt;: fewer tool calls, instant symbol lookups, 100% local.&lt;/p&gt;

&lt;p&gt;Inspired by &lt;a href="https://github.com/colbymchenry/codegraph" rel="noopener noreferrer"&gt;CodeGraph&lt;/a&gt; by &lt;a href="https://github.com/colbymchenry" rel="noopener noreferrer"&gt;colbymchenry&lt;/a&gt; for Claude Code, rebuilt natively for Kiro's MCP and hooks system.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Why KiroGraph?&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;When you ask Kiro to work on a complex task, it explores your codebase using file reads, grep, and glob searches. Every one of those is a tool call, and tool calls consume context and slow things down.&lt;/p&gt;

&lt;p&gt;KiroGraph gives Kiro a semantic knowledge graph that's pre-indexed and always up to date. Instead of scanning files to understand your code, Kiro queries the graph instantly: symbol relationships, call graphs, type hierarchies, impact radius — all in a single MCP tool call.&lt;/p&gt;

&lt;p&gt;The result is fewer tool calls, less context used, and faster responses on complex tasks.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What Gets Indexed?&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;KiroGraph uses &lt;a href="https://tree-sitter.github.io/tree-sitter/" rel="nofollow noopener noreferrer"&gt;tree-sitter&lt;/a&gt; to parse your source files into an AST and extract:&lt;/p&gt;


&lt;ul&gt;

&lt;li&gt;

&lt;strong&gt;Nodes&lt;/strong&gt; — functions, methods…&lt;/li&gt;

&lt;/ul&gt;
&lt;/div&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/davide-desio-eleva/kirograph" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;The installer will walk you through everything. If you're not sure which engine to pick, start with &lt;code&gt;cosine&lt;/code&gt;, it works out of the box with no dependencies and you can always switch later. If you're using it on large codebases, pick &lt;code&gt;pglite&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The repo is public. If you build on it, find a bug, or have thoughts on the engine choices, I'd love to hear from you.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A very special thanks to the &lt;a href="https://github.com/davide-desio-eleva/kirograph/stargazers" rel="noopener noreferrer"&gt;Stargazers&lt;/a&gt;, your support means a lot and truly makes a difference.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🙋 Who am I
&lt;/h2&gt;

&lt;p&gt;I'm &lt;a href="https://www.linkedin.com/in/desiodavide" rel="noopener noreferrer"&gt;D. De Sio&lt;/a&gt; and I work as a Head of Software Engineering in &lt;a href="https://eleva.it/" rel="noopener noreferrer"&gt;Eleva&lt;/a&gt;.&lt;br&gt;
As of Feb 2026, I’m an &lt;a href="https://www.credly.com/badges/9929fdf2-7a3d-4013-9de6-57c80e4920b9/public_url" rel="noopener noreferrer"&gt;AWS Certified Solution Architect Professional&lt;/a&gt; and &lt;a href="https://www.credly.com/badges/8c5a1487-191b-429e-8c2d-7cee43bf316b/public_url" rel="noopener noreferrer"&gt;AWS Certified DevOps Engineer Professional&lt;/a&gt;, but also a &lt;a href="https://www.linkedin.com/company/aws-user-group-pavia/" rel="noopener noreferrer"&gt;User Group Leader (in Pavia)&lt;/a&gt;, an &lt;strong&gt;AWS Community Builder&lt;/strong&gt; and, last but not least, a #serverless enthusiast.&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;It’s always amusing how AI is convinced developers are basically 80% coffee, 20% code, and somehow still functional.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>kiro</category>
      <category>aws</category>
      <category>ai</category>
    </item>
    <item>
      <title>AWS Lambda Durable Functions vs Step Functions: a real-world comparison</title>
      <dc:creator>Davide De Sio</dc:creator>
      <pubDate>Mon, 23 Feb 2026 18:03:06 +0000</pubDate>
      <link>https://dev.to/aws-builders/aws-lambda-durable-functions-vs-step-functions-a-real-world-comparison-5gij</link>
      <guid>https://dev.to/aws-builders/aws-lambda-durable-functions-vs-step-functions-a-real-world-comparison-5gij</guid>
      <description>&lt;p&gt;Hey devs, I recently built the same order dispatch workflow twice, once with &lt;strong&gt;AWS Step Functions&lt;/strong&gt; and once with &lt;strong&gt;AWS Lambda durable functions&lt;/strong&gt;. The difference in developer experience was significant. Let me walk you through what I learned and why I decided to do this.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;AWS Lambda durable functions&lt;/code&gt; &lt;a href="https://aws.amazon.com/about-aws/whats-new/2025/12/lambda-durable-functions-14-additional-regions/" rel="noopener noreferrer"&gt;are relatively new to the AWS ecosystem&lt;/a&gt;, so deciding whether to use them is not always straightforward.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  🎯 The Problem: A Real-World Order Workflow
&lt;/h2&gt;

&lt;p&gt;I needed to build a simple workflow for handling an order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Store the order in &lt;code&gt;DynamoDB&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Check inventory &lt;/li&gt;
&lt;li&gt;Wait for human approval&lt;/li&gt;
&lt;li&gt;Automatically find alternatives if rejected or complementary items if confirmed&lt;/li&gt;
&lt;li&gt;Wait 2 days &lt;/li&gt;
&lt;li&gt;Generate an email with &lt;code&gt;Bedrock&lt;/code&gt; (with alternatives or complementary items, based on confirmation or rejection) &lt;/li&gt;
&lt;li&gt;Send it via &lt;code&gt;SES&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is a real-world scenario (and also something I needed in production): human-in-the-loop, approvals, timers, and external service integrations. In reality, it’s a bit more complex than that, but for the sake of discussion, we can focus on this key question: should I choose &lt;code&gt;Step Functions&lt;/code&gt; or &lt;code&gt;Lambda durable functions&lt;/code&gt;?&lt;/p&gt;

&lt;h2&gt;
  
  
  ⚡ Which one should I choose?
&lt;/h2&gt;

&lt;p&gt;The choice framework proposed by AWS is a good start:&lt;/p&gt;

&lt;p&gt;Go with &lt;code&gt;Lambda durable functions&lt;/code&gt; if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;You prefer using your familiar programming language&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Local testing without cloud dependencies is important to you&lt;/li&gt;
&lt;li&gt;Your compute service of choice is AWS Lambda and your business logic primarily lives in those functions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stick with &lt;code&gt;Step Functions&lt;/code&gt; if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Visual workflows are important for your stakeholders&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;You're orchestrating many AWS services together&lt;/li&gt;
&lt;li&gt;You want to reduce ops burden (patching, scaling, ..)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I wasn’t sure what to choose, as my goal is always to maximize Developer Experience (DevEx) and maintainability. I don’t necessarily need a fully visual workflow, but one of the business requirements is ensuring seamless integration with other AWS services and upcoming workflows. I’m also a big fan of &lt;code&gt;Step Functions&lt;/code&gt; when it comes to &lt;code&gt;CDK&lt;/code&gt;-based projects. On the other hand, I’m really drawn to the simplicity of &lt;code&gt;Lambda durable functions&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  🆚 Code comparison
&lt;/h2&gt;

&lt;p&gt;So, this was the perfect scenario to explore both solutions. The workflow is clear and simple enough that it won’t take much time to build them in parallel, allowing for a real-world comparison.&lt;/p&gt;

&lt;p&gt;Let’s look at the actual code: this is where the differences become clear.&lt;/p&gt;

&lt;h3&gt;
  
  
  A TypeScript function using Lambda durable functions
&lt;/h3&gt;

&lt;p&gt;Why does this perfectly suit my scenario?&lt;/p&gt;

&lt;p&gt;It’s a single &lt;code&gt;TypeScript&lt;/code&gt; function, there’s no &lt;code&gt;CDK&lt;/code&gt; involved to implement the workflow. &lt;code&gt;CDK&lt;/code&gt; is used only to create the architecture, thus a single &lt;code&gt;Lambda&lt;/code&gt;, cleanly separating my workflow logic from the infrastructure code. &lt;/p&gt;

&lt;p&gt;Using &lt;code&gt;async/await&lt;/code&gt; feels very natural for a developer working in this environment, and I can encapsulate the entire workflow within a single function, giving me one clear place to understand what’s going on. On top of that, I get full IDE support with autocomplete and type checking (and AI, have you tried &lt;a href="https://kiro.dev" rel="noopener noreferrer"&gt;Kiro&lt;/a&gt; yet?)&lt;/p&gt;

&lt;p&gt;Let’s take a look at the code, starting with the imports.&lt;br&gt;
I’ll remove all the pure business logic, as we should focus on the workflow itself (and I can’t disclose my client’s code!).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;withDurableExecution&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;DurableContext&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@aws/durable-execution-sdk-js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since we’ve imported it, we should wrap the handler in a durable execution.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;withDurableExecution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;OrderEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DurableContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can now move on to our workflow steps. &lt;br&gt;
Let’s start by saving the order (in my real-world scenario I've saved it to &lt;code&gt;DynamoDB&lt;/code&gt; using the &lt;code&gt;AWS SDK&lt;/code&gt;) with a first async step.&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;// Step 1: Save order&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;save-order&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;OrderData&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;orderId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`ORD-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;buyerEmail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;buyerEmail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second step waits for the first one to complete, then checks the inventory and provides the relevant information needed to confirm or reject the order. An interesting aspect is that we can use a logger to record the response of each step and async/await pattern really helps us understand what happens in sequence.&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;// Step 2: Check inventory&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;availability&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;check-inventory&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;itemId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;itemId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;available&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;inStock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Inventory checked&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;availability&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The third step handles human approval using &lt;code&gt;waitForCallback&lt;/code&gt; function. At this stage, in my real-world scenario, an email is sent to the approver (I used &lt;code&gt;SES&lt;/code&gt;, but you could just as easily use an &lt;code&gt;SNS&lt;/code&gt; topic or any other notification system). However, this is just part of the business logic, which I won’t go into here.&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;// Step 3: Wait for human approval (up to 48h, no compute cost)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;approval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wait-for-approval&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callbackId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Waiting for approval&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;callbackId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;orderId&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="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;hours&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;48&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the decision is received, we handle any rejected items and look for suitable alternatives (I retrieved them from &lt;code&gt;DynamoDB&lt;/code&gt;, but you can use any database you prefer). While, if the order is accepted, we instead search for complementary items to recommend to the user.&lt;br&gt;
For simplicity, error handling is omitted here, but in a production scenario this should be wrapped in a try/catch block and handled properly.&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;// Step 4: Handle approval or rejection&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;approval&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;suggestedItems&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;discard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;suggestedItems&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;find-similar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;itemId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SIM-1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Similar Item&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}];&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;suggestedItems&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;find-complementary&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;itemId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;COM-1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Complementary Item&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}];&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The business then decided to pause the workflow for at least two days, as they don’t want to bother the user with marketing emails immediately after an order is accepted or rejected. This is a good opportunity to see a &lt;code&gt;wait&lt;/code&gt; in action.&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;// Step 5: Wait 2 days before marketing follow-up&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wait-two-days&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;days&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the wait is over, I generate the email via &lt;code&gt;Amazon Bedrock&lt;/code&gt;, using information from previous steps or the database. In practice, I personalize the message based on the approval decision, either suggesting similar products for rejected orders or recommending complementary items for confirmed ones.&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;// Step 6: Generate marketing email via Bedrock&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;generate-email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;This is where the email has been generate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally send the email generated and close the function returning the order, status and suggested items.&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;// Step 7: Send email&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;send-email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sending email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;buyerEmail&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;suggested&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;suggestedItems&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Approach with Step Function
&lt;/h3&gt;

&lt;p&gt;Here's the &lt;strong&gt;same&lt;/strong&gt; workflow implemented using &lt;code&gt;Step Functions&lt;/code&gt; and &lt;code&gt;CDK&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;First, we need to set up all the required &lt;code&gt;Lambda&lt;/code&gt; functions. Doesn’t that feel a bit odd? We want to define the workflow, yet we’re forced to create every individual function before we’ve even written the workflow itself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderDispatchStepFunctionStack&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Stack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Construct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;StackProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;saveOrderFunction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SaveOrderFunction&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODEJS_20_X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;save-order.handler&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Code&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromAsset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&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="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../lambda&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;checkInventoryFunction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CheckInventoryFunction&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODEJS_20_X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;check-inventory.handler&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Code&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromAsset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&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="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../lambda&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;findSimilarItemsFunction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;FindSimilarItemsFunction&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODEJS_20_X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;find-similar-items.handler&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Code&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromAsset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&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="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../lambda&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;findComplementaryItemsFunction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;FindComplementaryItemsFunction&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODEJS_20_X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;find-complementary-items.handler&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Code&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromAsset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&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="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../lambda&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;generateEmailFunction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GenerateEmailFunction&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODEJS_20_X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;generate-email.handler&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Code&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromAsset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&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="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../lambda&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sendEmailFunction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SendEmailFunction&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODEJS_20_X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;send-email.handler&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Code&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromAsset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&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="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../lambda&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I’ve omitted the permission setup here to keep things simple. Just remember that each Lambda function still needs the appropriate IAM permissions to access the required AWS services. And this is again a lot of boilerplate code.&lt;/p&gt;

&lt;p&gt;Finally, we can define our tasks. Again, this feels mostly like boilerplate: just a way to wrap each individual Lambda function.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;saveOrderTask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LambdaInvoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SaveOrder&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;lambdaFunction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;saveOrderFunction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;outputPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$.Payload&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;checkInventoryTask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LambdaInvoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CheckInventory&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;lambdaFunction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;checkInventoryFunction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;outputPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$.Payload&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sendApprovalNotification&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SnsPublish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SendApprovalNotification&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;approvalTopic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sfn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TaskInput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromJsonPathAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;waitForApproval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;sfn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;WaitForHumanApproval&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sfn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WaitTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;minutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;findSimilarTask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LambdaInvoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;FindSimilarItems&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;lambdaFunction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;findSimilarItemsFunction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;outputPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$.Payload&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;findComplementaryTask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LambdaInvoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;FindComplementaryItems&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;lambdaFunction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;findComplementaryItemsFunction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;outputPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$.Payload&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;waitTwoDays&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;sfn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;WaitTwoDays&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sfn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WaitTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;generateEmailTask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LambdaInvoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GenerateEmail&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;lambdaFunction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;generateEmailFunction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;outputPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$.Payload&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sendEmailTask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LambdaInvoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SendEmail&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;lambdaFunction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sendEmailFunction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;outputPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$.Payload&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we introduce a bit of workflow logic, mainly to define how the approval step should be handled.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;approvalChoice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;sfn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ApprovalDecision&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rejectedFlow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;findSimilarTask&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;sfn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Pass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;OrderRejected&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sfn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromObject&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rejected&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
        &lt;span class="na"&gt;resultPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$.orderStatus&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;confirmedFlow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;findComplementaryTask&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;sfn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Pass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;OrderAccepted&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sfn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromObject&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;accepted&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
        &lt;span class="na"&gt;resultPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$.orderStatus&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;approvalChoice&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;when&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sfn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Condition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$.decision&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;confirm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;confirmedFlow&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;when&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sfn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Condition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$.decision&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;discard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;rejectedFlow&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;otherwise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rejectedFlow&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now, we bring it all together into the final, straightforward workflow.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;definition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;saveOrderTask&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;checkInventoryTask&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sendApprovalNotification&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;waitForApproval&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;approvalChoice&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;waitTwoDays&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;generateEmailTask&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sendEmailTask&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stateMachine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;sfn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;StateMachine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;OrderDispatchStateMachine&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;definition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first thing I immediately notice is &lt;strong&gt;the amount of boilerplate required just to prepare each Lambda before even starting to think about the workflow itself.&lt;/strong&gt; And also, this is just the workflow part, you’ll also have the business logic implemented inside the &lt;code&gt;Lambda&lt;/code&gt; functions.&lt;/p&gt;

&lt;p&gt;I now have a lot of separate Lambda functions to maintain, and this has always been my main concern with &lt;code&gt;Step Functions&lt;/code&gt;, &lt;strong&gt;the workflow logic still ends up mixed with pure infrastructure code.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I have an almost Shakespearean dilemma that keeps me up at night: is the workflow a matter of architecture or business?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The business logic is spread across multiple files, and while that’s perfectly fine (separation of concerns is still a best practice and you should implement it also when using &lt;code&gt;Lambda durable functions&lt;/code&gt;), it makes it much harder to understand the workflow as &lt;strong&gt;you lose the ability to see the entire flow at a glance, and understanding it properly often requires a certain level of expertise, at least with &lt;code&gt;ASL&lt;/code&gt;.&lt;/strong&gt;&lt;br&gt;
You can see it clearly in the &lt;code&gt;CDK&lt;/code&gt; code, but here you have it mixed up with pure architecture code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And do you know what happens when &lt;code&gt;Step Functions&lt;/code&gt; doesn’t support something I need? I end up writing that logic directly inside the Lambdas.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;This happens when integrating new services that aren’t supported by &lt;code&gt;Step Functions&lt;/code&gt;, implementing complex data transformation logic, handling advanced catch/retry scenarios beyond what the service offers, or simply when something is difficult to express in &lt;code&gt;ASL&lt;/code&gt; but straightforward to implement in code inside a &lt;code&gt;Lambda&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;Basically I create another step with a &lt;code&gt;Lambda&lt;/code&gt; to do this work.&lt;/p&gt;

&lt;p&gt;In doing so, I lose all the benefits I chose &lt;code&gt;Step Functions&lt;/code&gt; for in the first place: separation of concerns, &lt;strong&gt;clear workflow visibility&lt;/strong&gt;, &lt;strong&gt;and predictable orchestration&lt;/strong&gt;, basically everything that made it the right choice to begin with.&lt;/p&gt;

&lt;p&gt;Want you see which is the &lt;code&gt;CDK&lt;/code&gt; needed for the &lt;code&gt;Durable Function&lt;/code&gt;?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-cdk-lib&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-cdk-lib/aws-lambda&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;iam&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-cdk-lib/aws-iam&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Construct&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;constructs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DurableFunctionStack&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Stack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Construct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;StackProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Create the durable function&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;durableFunction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DurableFunction&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODEJS_22_X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;index.handler&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Code&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromAsset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lambda&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;durableConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;executionTimeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hours&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="na"&gt;retentionPeriod&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&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;// Create version and alias&lt;/span&gt;
    &lt;span class="kd"&gt;const&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;durableFunction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentVersion&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;alias&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Alias&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ProdAlias&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;aliasName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;prod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And this is pure architecture: &lt;strong&gt;no workflow logic mixed in&lt;/strong&gt;. It can live alongside other core architecture components, like DynamoDB, S3, IAM permissions, and so on. Here is a full working architecture sample.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-cdk-lib&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;dynamodb&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-cdk-lib/aws-dynamodb&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-cdk-lib/aws-lambda&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;logs&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-cdk-lib/aws-logs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;iam&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-cdk-lib/aws-iam&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Construct&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;constructs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderDispatchDurableStack&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Stack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Construct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;StackProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// DynamoDB Tables&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ordersTable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;OrdersTable&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;partitionKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;orderId&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AttributeType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STRING&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;billingMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BillingMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PAY_PER_REQUEST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;removalPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RemovalPolicy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DESTROY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;inventoryTable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;InventoryTable&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;partitionKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;itemId&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AttributeType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STRING&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;billingMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BillingMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PAY_PER_REQUEST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;removalPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RemovalPolicy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DESTROY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;inventoryTable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addGlobalSecondaryIndex&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;indexName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CategoryIndex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;partitionKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;category&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AttributeType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STRING&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Log Group for Durable Function&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;orchestratorLogGroup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;logs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LogGroup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;OrchestratorLogGroup&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;logGroupName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/aws/lambda/order-dispatch-orchestrator&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;retention&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;logs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RetentionDays&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ONE_WEEK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;removalPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RemovalPolicy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DESTROY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Main Orchestrator Durable Function&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;orchestratorFunction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;OrchestratorFunction&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODEJS_24_X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;orchestrator.handler&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Code&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromAsset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&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="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../lambda&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;minutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;logGroup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;orchestratorLogGroup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;durableConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;executionTimeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;retentionPeriod&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;ORDERS_TABLE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ordersTable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;INVENTORY_TABLE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;inventoryTable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;SENDER_EMAIL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SENDER_EMAIL&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;noreply@example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;ordersTable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;grantReadWriteData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orchestratorFunction&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;inventoryTable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;grantReadData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orchestratorFunction&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;orchestratorFunction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addToRolePolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;iam&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PolicyStatement&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bedrock:InvokeModel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;}));&lt;/span&gt;

    &lt;span class="nx"&gt;orchestratorFunction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addToRolePolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;iam&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PolicyStatement&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ses:SendEmail&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ses:SendRawEmail&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;}));&lt;/span&gt;

    &lt;span class="c1"&gt;// Create version and alias&lt;/span&gt;
    &lt;span class="kd"&gt;const&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;orchestratorFunction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentVersion&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;alias&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Alias&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ProdAlias&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;aliasName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;prod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Outputs&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CfnOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;OrchestratorFunctionArn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;alias&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;functionArn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Use this qualified ARN to invoke the durable function&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CfnOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;OrdersTableName&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ordersTable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CfnOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;InventoryTableName&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;inventoryTable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I love it. This is just architectural code. The actual workflow logic isn’t included here.&lt;/p&gt;

&lt;h2&gt;
  
  
  🧪 A crucial point in DevEx: testing
&lt;/h2&gt;

&lt;p&gt;Ok, let's try to go deeper. I've written the code for both solutions, both come with trade-offs. &lt;strong&gt;Now I should test it before even thinking about deploying.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is where &lt;code&gt;Lambda durable functions&lt;/code&gt; really stands out for me: it feels as straightforward as using Node's &lt;code&gt;test runner&lt;/code&gt;, &lt;code&gt;Jest&lt;/code&gt; or any other testing framework we’re already familiar with.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test locally with node on Durable Functions
&lt;/h3&gt;

&lt;p&gt;Having just a single function is a big advantage because there’s no AWS infrastructure involved, and no need to mock &lt;code&gt;Step Functions&lt;/code&gt;. You simply write your tests and run &lt;code&gt;npm test&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let’s start by importing the necessary libraries.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;LocalDurableTestRunner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;WaitingOperationStatus&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@aws/durable-execution-sdk-js-testing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;OperationType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;OperationStatus&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@aws-sdk/client-lambda&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../orchestrator&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we can create the test suite by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;initializing the test environment using the &lt;code&gt;setupTestEnvironment&lt;/code&gt; function and passing skipTime: true in Jest’s &lt;code&gt;beforeAll&lt;/code&gt; hook.&lt;/li&gt;
&lt;li&gt;tearing down the test environment using &lt;code&gt;teardownTestEnvironment&lt;/code&gt; in Jest’s &lt;code&gt;afterAll&lt;/code&gt; hook.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Order Dispatch Durable Function&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;beforeAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;LocalDurableTestRunner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setupTestEnvironment&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;skipTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;afterAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;LocalDurableTestRunner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;teardownTestEnvironment&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are now ready to initialize our test. In this case, the scope is completing the workflow with confirmation. Let’s define the runner and connect it to the imported handler.&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="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should execute complete workflow with approval&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;runner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;LocalDurableTestRunner&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;handlerFunction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we define our &lt;code&gt;orderEvent&lt;/code&gt; (i.e., the incoming order).&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;orderEvent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;buyerEmail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;customer@example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;itemId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ITM-1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can now start the execution on the runner, passing our &lt;code&gt;orderEvent&lt;/code&gt;, and wait for it to complete.&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;// Start execution (will pause at callback)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;executionPromise&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;runner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;orderEvent&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since we have a human in the loop to simulate, we can use &lt;code&gt;runner.getOperation&lt;/code&gt; to get the callback operation and wait until it is &lt;code&gt;STARTED&lt;/code&gt;. Then we submit our decision with sendCallbackSuccess and wait for it to be &lt;code&gt;COMPLETED&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;    &lt;span class="c1"&gt;// Get callback operation and wait for it to be ready&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;callbackOp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;runner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getOperation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wait-for-approval&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;callbackOp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;WaitingOperationStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STARTED&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Send approval callback&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;callbackOp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendCallbackSuccess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;decision&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;confirm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;callbackOp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;WaitingOperationStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;COMPLETED&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, we wait for the execution to finish and verify the expected outcome (in this case, that the order is confirmed).&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;// Wait for execution to complete&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;execution&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;executionPromise&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Verify execution succeeded&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;execution&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getStatus&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SUCCEEDED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;execution&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getResult&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toMatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^ORD-/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;confirm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To complete our test, we can also verify that the other steps executed successfully using &lt;code&gt;runner.getOperation&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;    &lt;span class="c1"&gt;// Verify operations executed&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;saveOrder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;runner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getOperation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;save-order&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;saveOrder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getType&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;OperationType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STEP&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;saveOrder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getStatus&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;OperationStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SUCCEEDED&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;checkInventory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;runner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getOperation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;check-inventory&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;checkInventory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getType&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;OperationType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STEP&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;checkInventory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getStatus&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;OperationStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SUCCEEDED&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;waitTwoDays&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;runner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getOperation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wait-two-days&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;waitTwoDays&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getType&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;OperationType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WAIT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;waitTwoDays&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getStatus&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;OperationStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SUCCEEDED&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;generateEmail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;runner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getOperation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;generate-email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;generateEmail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getType&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;OperationType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STEP&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;generateEmail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getStatus&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;OperationStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SUCCEEDED&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sendEmail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;runner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getOperation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;send-email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sendEmail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getType&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;OperationType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STEP&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sendEmail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getStatus&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;OperationStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SUCCEEDED&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;In the end, writing and testing a durable workflow like this is surprisingly simple and enjoyable. With just a single function and the local test runner, you don’t have to deal with complex AWS infrastructure or mocking, and the code remains clear and easy to follow. It’s genuinely satisfying to see the entire workflow execute and verify each step with minimal setup.&lt;/p&gt;

&lt;h3&gt;
  
  
  Local testing has always been challenging for Step Functions
&lt;/h3&gt;

&lt;p&gt;Instead, we have a few options when it comes to testing something implemented with &lt;code&gt;Step Functions&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Remotely&lt;/strong&gt;, by deploying to AWS and testing against real infrastructure.&lt;br&gt;
&lt;strong&gt;Locally&lt;/strong&gt;, using frameworks or tools that simulate &lt;code&gt;Step Functions&lt;/code&gt;.&lt;br&gt;
Or &lt;strong&gt;Unit testing&lt;/strong&gt;, by testing each Lambda function individually.&lt;/p&gt;

&lt;p&gt;However, even with these approaches, we’re still missing proper end-to-end testing of the entire workflow, which is often the most critical part to validate.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Option 1: Deploy to AWS and test remotely (slow, costs money)&lt;/span&gt;
aws stepfunctions start-execution &lt;span class="nt"&gt;--state-machine-arn&lt;/span&gt; arn:aws:...

&lt;span class="c"&gt;# Option 2: Use Step Functions Local (limited, requires Docker)&lt;/span&gt;
docker run &lt;span class="nt"&gt;-p&lt;/span&gt; 8083:8083 amazon/aws-stepfunctions-local
&lt;span class="c"&gt;# Still need to mock all Lambda functions&lt;/span&gt;
&lt;span class="c"&gt;# Still need to mock DynamoDB, SNS, SES...&lt;/span&gt;

&lt;span class="c"&gt;# Option 3: Unit test each Lambda separately&lt;/span&gt;
&lt;span class="c"&gt;# But you can't test the workflow orchestration!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;All these options make rapid iteration much harder&lt;/strong&gt;. Only the first approach truly gives me confidence that I’ve tested the workflow end-to-end. But this forces me to mentally switch between deployment and testing, which breaks flow and slows down development. For me, that kind of friction is a productivity killer.&lt;/p&gt;

&lt;p&gt;There is a clear winner for me here, and it's not &lt;code&gt;Step Functions&lt;/code&gt;, &lt;a href="https://aws.amazon.com/about-aws/whats-new/2025/11/aws-step-functions-local-testing-teststate-api/" rel="noopener noreferrer"&gt;while testing continues to improve&lt;/a&gt; thanks to AWS folks.&lt;/p&gt;

&lt;h2&gt;
  
  
  💻 What bothers devs: after deploy ops.
&lt;/h2&gt;

&lt;p&gt;Both the business, and sometimes we developers as well, tend to underestimate the importance of day-to-day operations after the first deployment. Production environments involve change requests, debugging, fixes, and monitoring.&lt;/p&gt;

&lt;p&gt;Here’s what the daily development workflow looks like with each tool in this phase.&lt;/p&gt;

&lt;h3&gt;
  
  
  Durable Functions
&lt;/h3&gt;

&lt;p&gt;What's going on if I receive a change request?&lt;/p&gt;

&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;Edit my function&lt;/li&gt;
&lt;li&gt;Write/update test&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;npm test&lt;/code&gt; (1 second to iterate)&lt;/li&gt;
&lt;li&gt;Deploy with &lt;code&gt;cdk deploy&lt;/code&gt; (very short time as this is just one function drift to be released)&lt;/li&gt;
&lt;li&gt;Only now invoke the function endpoint to be sure everything is ok.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;What will I do if I need to debug anything?&lt;/p&gt;

&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;Look in console (or get via CLI) &lt;code&gt;CloudWatch&lt;/code&gt; logs &lt;strong&gt;in a single log group&lt;/strong&gt;: anyone using &lt;code&gt;CloudWatch&lt;/code&gt; should know the nightmare of navigating multiple logs groups. Also, the &lt;code&gt;Lambda durable functions&lt;/code&gt; console surfaces logs and execution details without having to jump to &lt;code&gt;CloudWatch&lt;/code&gt;, letting you focus on your single &lt;code&gt;Lambda&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;See complete execution flow in one place&lt;/li&gt;
&lt;li&gt;Replay the execution locally with tests and see what's broken&lt;/li&gt;
&lt;li&gt;Fix the bug, run test, deploy.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;What if I need to onboard another developer, regardless of seniority?&lt;/p&gt;

&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;Share the single function and walk the developer through the code&lt;/li&gt;
&lt;li&gt;They will probably understand it quickly, since it’s just a single function.&lt;/li&gt;
&lt;li&gt;Hopefully they can contribute within hours, test it locally, give me a PR which contains the "full micro-service workflow" logic without having to change a line of architecture.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;Moreover, now that we live in the era of AI coding assistants, AI powered IDEs, and autonomous agents for coding, it has never been easier to onboard new developers. Providing them with precise, focused context around a single Lambda function is undoubtedly one of the most effective ways to get them productive in a very short time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I wouldn’t be surprised if we soon see dedicated Kiro Power-Ups and SOPs for Durable Functions in the AWS MCP ecosystem.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step Functions
&lt;/h3&gt;

&lt;p&gt;Let's see the similar dev scenarios with &lt;code&gt;Step Functions&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;What's going on if I need to implement a change request?&lt;/p&gt;

&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;Modify state machine definition in CDK with &lt;code&gt;ASL&lt;/code&gt; language&lt;/li&gt;
&lt;li&gt;Modify one or multiple Lambda functions&lt;/li&gt;
&lt;li&gt;No quick way to test locally (or should have a setup to do it, and so your teammates)&lt;/li&gt;
&lt;li&gt;Deploy with &lt;code&gt;cdk deploy&lt;/code&gt; (2-3 minutes as the drift would be much more than a single &lt;code&gt;Lambda&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Go and test manually in AWS Console to be sure of the implementation, but get back to the code if anything isn't right (oh my..)&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;And if I catch an error and I should debug it?&lt;/p&gt;

&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;Again open &lt;code&gt;Step Functions&lt;/code&gt; execution in AWS Console&lt;/li&gt;
&lt;li&gt;Click through each state to see input/output&lt;/li&gt;
&lt;li&gt;Open &lt;code&gt;CloudWatch&lt;/code&gt; logs for relevant &lt;code&gt;Lambda&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Correlate timestamps across services&lt;/li&gt;
&lt;li&gt;Maybe use &lt;code&gt;X-Ray&lt;/code&gt; for tracing&lt;/li&gt;
&lt;li&gt;Fix the bug, redeploy, restart again.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;Don't get me wrong, that’s perfectly fine. I genuinely like &lt;code&gt;Step Functions&lt;/code&gt; because, despite those "velocity" trade-offs in DevEx, they enforce a proper orchestration of distributed systems. They also provide a clear visualization of the workflow in the console, make it easy to catch errors at the failing state, and help you understand what’s happening inside complex orchestrations, ultimately simplifying what would otherwise be a very intricate system.&lt;/p&gt;

&lt;p&gt;But what if I need to onboard someone who isn’t an AWS expert and isn’t very familiar with &lt;code&gt;Step Functions&lt;/code&gt; or workflow architecture in general?&lt;/p&gt;

&lt;p&gt;I’d have to:&lt;/p&gt;

&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;Introduce &lt;code&gt;Amazon States Language&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Explain each &lt;code&gt;Lambda&lt;/code&gt; functions&lt;/li&gt;
&lt;li&gt;Walk through the &lt;code&gt;CDK&lt;/code&gt; stack&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;This means that a developer would typically become productive only after a few days, and it really depends on their seniority and prior experience with AWS. Trust me, this can easily become a waste of time and a nightmare, both from the mentor’s perspective and the learner’s.&lt;/p&gt;

&lt;p&gt;From a DevEx perspective, &lt;code&gt;Lambda durable functions&lt;/code&gt; are a major step forward.&lt;/p&gt;

&lt;h2&gt;
  
  
  🤔 So when do Step Functions still make sense?
&lt;/h2&gt;

&lt;p&gt;However, &lt;code&gt;Lambda durable functions&lt;/code&gt; won’t always be the right answer.&lt;br&gt;
&lt;code&gt;Step Functions&lt;/code&gt; has genuine advantages in two main cases.&lt;/p&gt;
&lt;h3&gt;
  
  
  Visual workflows matter
&lt;/h3&gt;

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

&lt;p&gt;One of the biggest advantages of &lt;code&gt;Step Functions&lt;/code&gt; is that stakeholders can see the workflow visually: this visual representation is not just a “nice-to-have.” It’s crucial for stakeholder demos, where non-technical team members can quickly understand the workflow and see how processes progress.&lt;/p&gt;

&lt;p&gt;It also simplifies compliance reviews as auditors can trace exactly what happens at each step without digging through code.&lt;/p&gt;

&lt;p&gt;What about operations monitoring? DevOps and support teams can spot failures or bottlenecks immediately, understand dependencies between steps, and react faster (sometimes without having access to the code itself).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In short, having a clear, visual workflow turns complex orchestration into something everyone can comprehend, communicate about, and trust.&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Native AWS Service Integration
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Step Functions&lt;/code&gt; has native integrations with 200+ AWS services:&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;// Directly invoke services without Lambda&lt;/span&gt;
&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DynamoPutItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SaveOrder&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ordersTable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SqsSendMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;QueueOrder&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;orderQueue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;messageBody&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sfn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TaskInput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromObject&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;EcsRunTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ProcessOrder&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ecsCluster&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;taskDefinition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;orderProcessor&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using &lt;code&gt;CDK&lt;/code&gt; you have a lot of options for simple task basically for each AWS Service.&lt;/p&gt;

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

&lt;p&gt;For AWS service orchestrations, this is actually pretty clean.&lt;br&gt;
While if you want to implement something directly in code with a &lt;code&gt;Lambda durable function&lt;/code&gt;, obviously you need to use the &lt;code&gt;SDK&lt;/code&gt;, and that logic becomes part of your business layer.&lt;/p&gt;
&lt;h2&gt;
  
  
  🚨 So when should we prefer &lt;code&gt;Lambda durable functions&lt;/code&gt;?
&lt;/h2&gt;

&lt;p&gt;First, when you need a &lt;strong&gt;code-first philosophy&lt;/strong&gt; based on widely used languages such as &lt;code&gt;TypeScript&lt;/code&gt; or &lt;code&gt;Python&lt;/code&gt;.&lt;br&gt;
You write orchestration in the same language as your business logic. No need to learn a domain-specific language. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Also, if you want a simple local development and testing option.&lt;/strong&gt;&lt;br&gt;
For the first time, you can test complex workflows locally without AWS infrastructure.&lt;/p&gt;

&lt;p&gt;There’s no need to learn &lt;code&gt;Amazon States Language&lt;/code&gt; (&lt;code&gt;ASL&lt;/code&gt;) and what used to feel awkward and complex is now trivial: you can define, modify, and visualize workflows in the code, without diving into verbose JSON or mastering intricate patterns.&lt;/p&gt;

&lt;p&gt;As for an example of complex nested workflows:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;runInChildContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;parent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;child1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;runInChildContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;child1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;grandchild&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;c1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;runInChildContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;grandchild&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;gc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Deeply nested orchestration - easy!&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;grandchild&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="nx"&gt;child1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What about parallel executions?&lt;br&gt;
As simple as using a map&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;// Process N items in parallel (N determined at runtime)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&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="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;process-items&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`process-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; 
      &lt;span class="nf"&gt;processItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;maxConcurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;completionConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;minSuccessful&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;toleratedFailureCount&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="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;throwIfError&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;allResults&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getResults&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or saga patterns for distributed transactions:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;compensations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;charge-payment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;chargeCustomer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;compensations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;refundCustomer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payment&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;inventory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;reserve-inventory&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reserveItems&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;compensations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;releaseItems&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inventory&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;shipment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;create-shipment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;shipOrder&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// All succeeded!&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Automatically compensate in reverse order&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;compensate&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;compensations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;compensate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;compensate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Remember you will be an early adopter!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Being on the cutting edge means the community is smaller and there are fewer examples available, but the ecosystem is growing rapidly. Documentation is still maturing, and you may encounter some rough edges along the way.&lt;/p&gt;

&lt;p&gt;On the other hand: AWS is actively improving it, you have the opportunity to adopt modern patterns early, you will get skills that will become more valuable over time.&lt;/p&gt;

&lt;h2&gt;
  
  
  🎯 My recommendation and final thoughts
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;For new projects, really consider &lt;code&gt;Lambda durable functions&lt;/code&gt;. It's not just hype for a new pattern: the developer experience and local testing capabilities are significant advantages.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Existing &lt;code&gt;Step Functions&lt;/code&gt;? No need to rush a migration. Try &lt;code&gt;Lambda durable functions&lt;/code&gt; for your next new workflow and compare the experience.&lt;/p&gt;

&lt;p&gt;Do you really have to choose between them? &lt;br&gt;
The short answer is no, and you shouldn't. &lt;br&gt;
You can use both solutions depending on your use case and also use a &lt;code&gt;Lambda durable function&lt;/code&gt; in a wider orchestration built with &lt;code&gt;Step Functions&lt;/code&gt;. This is a great pattern for creating ‘leaf’ workflows focused on a specific concern, decoupled from others. You can "enforce" architectural decoupling when needed and benefit from a single Lambda’s advantages when convenient.&lt;/p&gt;

&lt;p&gt;Let’s be clear: both tools work. &lt;br&gt;
But they reflect different eras of &lt;code&gt;serverless&lt;/code&gt; thinking:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Step Functions&lt;/strong&gt; (2016): It's a safe, proven choice. Visual workflows, mature ecosystem, smooth integration with AWS ecosystem, battle-tested. It is still a very good choice for mature and ops teams.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Durable Functions&lt;/strong&gt; (2025): Code-first, local testing, modern patterns. Good for devs, new workflows, specific ones to integrate in a wider orchestration and time-to-market.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After spending weeks working with both, I can say that, to me, &lt;strong&gt;Durable Functions feels like where serverless orchestration should've been all along&lt;/strong&gt;. &lt;/p&gt;
&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/durable-functions.html" rel="noopener noreferrer"&gt;AWS Lambda durable functions Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/step-functions/latest/dg/welcome.html" rel="noopener noreferrer"&gt;AWS Step Functions Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also don't miss this awesome presentation video by Michael Gasch and Eric Johnson at the latest re:Invent in Dec 2025.&lt;/p&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/XJ80NBOwsow"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

&lt;p&gt;If you'd like a walkthrough of this excellent presentation, you can find one on &lt;a href="https://repost.aws/articles/ARc8wmu4l9TKywZCHX-_nn6w/re-invent-2025-deep-dive-on-aws-lambda-durable-functions" rel="noopener noreferrer"&gt;re:Post&lt;/a&gt; or an autogenerated &lt;a href="https://dev.to/kazuya_dev/aws-reinvent-2025-new-launch-deep-dive-on-aws-lambda-durable-functions-cns380-3edi"&gt;here on dev.to&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  🙋 Who am I
&lt;/h2&gt;

&lt;p&gt;I'm &lt;a href="https://www.linkedin.com/in/desiodavide" rel="noopener noreferrer"&gt;D. De Sio&lt;/a&gt; and I work as a Head of Software Engineering in &lt;a href="https://eleva.it/" rel="noopener noreferrer"&gt;Eleva&lt;/a&gt;.&lt;br&gt;
As of Feb 2026, I’m an &lt;a href="https://www.credly.com/badges/9929fdf2-7a3d-4013-9de6-57c80e4920b9/public_url" rel="noopener noreferrer"&gt;AWS Certified Solution Architect Professional&lt;/a&gt; and &lt;a href="https://www.credly.com/badges/8c5a1487-191b-429e-8c2d-7cee43bf316b/public_url" rel="noopener noreferrer"&gt;AWS Certified DevOps Engineer Professional&lt;/a&gt;, but also a &lt;a href="https://www.linkedin.com/company/aws-user-group-pavia/" rel="noopener noreferrer"&gt;User Group Leader (in Pavia)&lt;/a&gt;, an &lt;strong&gt;AWS Community Builder&lt;/strong&gt; and, last but not least, a #serverless enthusiast.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For those of you who’ve made it this far, I’m not exactly the person in the image (and for those who know me, just not in this photo!) but it’s always fun and interesting to see how GenAI imagines you.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>aws</category>
      <category>lambda</category>
      <category>stepfunctions</category>
      <category>durablefunctions</category>
    </item>
    <item>
      <title>Nevermore.dev: LLM-as-judge on Lambda Durable Functions</title>
      <dc:creator>Davide De Sio</dc:creator>
      <pubDate>Tue, 27 Jan 2026 17:47:03 +0000</pubDate>
      <link>https://dev.to/aws-builders/nevermoredev-llm-as-judge-on-lambda-durable-functions-2j22</link>
      <guid>https://dev.to/aws-builders/nevermoredev-llm-as-judge-on-lambda-durable-functions-2j22</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“The past is dead. The future? Let’s make it less painful.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Writing post‑mortems is one of those things everyone agrees are important and everyone secretly hates doing. They’re tedious, emotionally draining, and they require the worst kind of energy: &lt;strong&gt;clear thinking after chaos&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;And you find yourself thinking both: “I want this to happen never more” and “I want to write this never more”. And in that quiet, the chaos lingers. Undebuggable, relentless, eternal.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia3.giphy.com%2Fmedia%2Fv1.Y2lkPTc5MGI3NjExN3RhNzh0bGk3d3JldnlhN3NyeXB2YTd2NngzMmF6ZW9oZzE0NGVxdyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw%2FcirKPyIvk9Wo6uoqnX%2Fgiphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia3.giphy.com%2Fmedia%2Fv1.Y2lkPTc5MGI3NjExN3RhNzh0bGk3d3JldnlhN3NyeXB2YTd2NngzMmF6ZW9oZzE0NGVxdyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw%2FcirKPyIvk9Wo6uoqnX%2Fgiphy.gif" width="500" height="298"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nevermore.dev&lt;/strong&gt; was born from that very specific kind of developer pain: a post‑mortem generator with two moods:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Professional&lt;/strong&gt;: calm, neutral, executive‑friendly (but so boring)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Creepy&lt;/strong&gt;: full Addams‑family vibes, because if we have to revisit horror, we might as well embrace it 🦇&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%2Fhjkslu6ar1kpm6ldksrs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhjkslu6ar1kpm6ldksrs.png" alt=" " width="800" height="618"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Dark UI aside, the interesting tech geeky part lives under the hood: brand new &lt;strong&gt;AWS Lambda Durable Function&lt;/strong&gt; powering an &lt;strong&gt;LLM‑as‑Judge&lt;/strong&gt; workflow on &lt;strong&gt;Amazon Bedrock (using Nova models)&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  🏗️ Architecture
&lt;/h2&gt;

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

&lt;p&gt;The solution I had in mind was fairly simple in shape, even if layered in execution.&lt;/p&gt;

&lt;p&gt;The flow starts from an &lt;code&gt;Amplify Gen 2&lt;/code&gt; frontend. An &lt;code&gt;AppSync GraphQL&lt;/code&gt; mutation triggers a lightweight &lt;code&gt;Lambda&lt;/code&gt; whose only job is to start the AI workflow, not to run it (it acts as the sync backend). From there, everything moves asynchronously into a &lt;code&gt;Durable Lambda&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This durable function is where the real logic lives&lt;/strong&gt;. Instead of relying on a single model, the workflow follows an &lt;strong&gt;LLM as a judge&lt;/strong&gt; pattern. Generation happens in parallel: a fast model produces a first candidate, while a more balanced one generates an alternative. The point here is diversity, not consensus.&lt;/p&gt;

&lt;p&gt;Once both candidates are available, a higher-quality model steps in as a judge. It evaluates the outputs and selects the best result, acting as a decision layer rather than a generator.&lt;/p&gt;

&lt;p&gt;All model calls go through &lt;code&gt;Amazon Bedrock&lt;/code&gt;, keeping the system decoupled and letting each model focus on what it does best: speed, balance, or quality.&lt;/p&gt;

&lt;p&gt;In this way, the main benefit I was aiming for was avoiding the setup of a &lt;code&gt;Step Function&lt;/code&gt; with all its inherent complexity, while still having pure code to manage a durable, asynchronous workflow directly inside a &lt;code&gt;Lambda&lt;/code&gt;, a &lt;code&gt;Durable Lambda&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  🕯️ A bit more about Nevermore.dev
&lt;/h2&gt;

&lt;p&gt;At its core, I wanted a simple but effective CRUD panel to help me manage post-mortems. I've &lt;code&gt;semi-vibed&lt;/code&gt; (using specs, refining it..) it with &lt;code&gt;Kiro&lt;/code&gt; with my personal &lt;a href="https://dev.to/aws-builders/building-a-kiro-power-for-aws-amplify-gen-2-54gk"&gt;Amplify Gen 2 Kiro Power&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The product comes with all the usual Amplify Gen 2 built-in features.&lt;br&gt;
Fully integrated with &lt;code&gt;Cognito&lt;/code&gt; for auth.&lt;/p&gt;

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

&lt;p&gt;CRUD operations featured by an API wiht &lt;code&gt;AppSync&lt;/code&gt; and &lt;code&gt;DynamoDb&lt;/code&gt; as storage.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnsuy3nhs762bxpmggfhw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnsuy3nhs762bxpmggfhw.png" alt=" " width="800" height="740"&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%2Fb6m79o9t8rg0edasbj1b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb6m79o9t8rg0edasbj1b.png" alt=" " width="800" height="618"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Everything deployed with just &lt;code&gt;npx amplify deploy&lt;/code&gt;.&lt;br&gt;
Awesome to cut-off time to production and give me the product I was searching for.&lt;/p&gt;
&lt;h2&gt;
  
  
  🤖 AI to the rescue
&lt;/h2&gt;

&lt;p&gt;The real core of the product, by the way, is using AI to generate clearer, more useful incident descriptions and root cause analyses (which is why I’m building and using it in the first place). What truly bores me about writing post-mortems isn’t the incident itself, but the ritual around it: &lt;strong&gt;finding the right tone, the right template, the right wording&lt;/strong&gt;. With AI, all of that can be &lt;strong&gt;reduced to a single prompt that produces exactly what I need, but relying on a single model often forces me to switch models, manually evaluate the output, or ask another model to judge it&lt;/strong&gt;. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It’s all still far too manual for something that’s supposed to be part of my daily routine.&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%2Fxh41mps9q6t4nvworovq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxh41mps9q6t4nvworovq.png" alt=" " width="800" height="835"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thus, I wanted the ability to parallelize multiple generations across different models, then use another model to evaluate the results and pick the best one, which is where the &lt;code&gt;Lambda Durable Function&lt;/code&gt; comes into play. Finally, I can have the best output to be immediately available in Markdown, ready to be copied into team cards or any other notification system.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;As expected, it wasn’t funny enough.&lt;/strong&gt;&lt;br&gt;
Post-mortems aren’t funny, at least, not yet.&lt;br&gt;
But they should &lt;strong&gt;NEVERMORE&lt;/strong&gt; be boring.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia4.giphy.com%2Fmedia%2Fv1.Y2lkPTc5MGI3NjExMnh3OHdkcGpnaG51OGl4MmloMmh3eTNzc2E4cXQ5bDdpdnI3NTQ0cCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw%2FKuIPUkypeEZPHuDyOI%2Fgiphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia4.giphy.com%2Fmedia%2Fv1.Y2lkPTc5MGI3NjExMnh3OHdkcGpnaG51OGl4MmloMmh3eTNzc2E4cXQ5bDdpdnI3NTQ0cCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw%2FKuIPUkypeEZPHuDyOI%2Fgiphy.gif" width="500" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As I was &lt;code&gt;semi-vibing&lt;/code&gt; the frontend with &lt;code&gt;Kiro&lt;/code&gt; and my personal &lt;a href="https://dev.to/aws-builders/building-a-kiro-power-for-aws-amplify-gen-2-54gk"&gt;Amplify Gen 2 Kiro Power&lt;/a&gt;, it only took a couple of prompts to add a button theme switch and fully embrace a creepy mode for dark theme users (aren't we talking about post-mortems?). Since I &lt;strong&gt;nevermore&lt;/strong&gt; wanted to write a dull post-mortem, what better muse than the ever-macabre Addams Family?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flcrs9j3dd7envuny4m4j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flcrs9j3dd7envuny4m4j.png" alt=" " width="800" height="835"&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%2Fvjmfume92qy3fh2n489m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvjmfume92qy3fh2n489m.png" alt=" " width="800" height="831"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Now reading a post-mortem in full Addams Family tone is incredibly satisfying, and I regret nothing.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia0.giphy.com%2Fmedia%2Fv1.Y2lkPTc5MGI3NjExaG1jZmNnaWE0cXpzbWo4c2k2N3lxZWhuYmhvNnZ2b214OTE1dG54NSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw%2FhrUeHm9PkCu3fM07bn%2Fgiphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia0.giphy.com%2Fmedia%2Fv1.Y2lkPTc5MGI3NjExaG1jZmNnaWE0cXpzbWo4c2k2N3lxZWhuYmhvNnZ2b214OTE1dG54NSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw%2FhrUeHm9PkCu3fM07bn%2Fgiphy.gif" width="480" height="270"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  🧱 The stack
&lt;/h2&gt;

&lt;p&gt;Okay, creepy awesome. But let’s forget about the UI for a moment and get to the underline technical part.&lt;br&gt;
Nevermore.dev is built entirely on AWS with a fairly modern setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Amplify Gen 2&lt;/strong&gt;: frontend and full‑stack wiring&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amazon Cognito&lt;/strong&gt;: authentication and authorization&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS AppSync&lt;/strong&gt;: GraphQL API&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DynamoDB&lt;/strong&gt;: NoSQL records persistence&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS Lambda Durable Functions&lt;/strong&gt;: AI orchestration layer&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amazon Bedrock (Nova)&lt;/strong&gt;: AI models engine for text generation &amp;amp; evaluations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As we said, the brain of the system is a single &lt;code&gt;Durable Lambda Function&lt;/code&gt; that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Generates multiple enhanced versions of a post‑mortem section&lt;/li&gt;
&lt;li&gt;Uses another LLM to &lt;em&gt;judge&lt;/em&gt; them&lt;/li&gt;
&lt;li&gt;Returns the best one&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All of this really happens &lt;strong&gt;inside one function&lt;/strong&gt;, with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;parallel execution&lt;/li&gt;
&lt;li&gt;checkpointing&lt;/li&gt;
&lt;li&gt;resumability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The point here is: no Step Functions. No external state machines. No need to define complex architectures or multiple lambdas.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That’s where &lt;code&gt;Durable Functions&lt;/code&gt; really shine.&lt;br&gt;
Let's see why comparing to &lt;code&gt;Step Functions&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  ⚔️ Why Lambda Durable Functions (instead of Step Functions)
&lt;/h2&gt;

&lt;p&gt;Traditionally, a workflow like this would scream &lt;code&gt;Step Functions&lt;/code&gt;.&lt;br&gt;
They work and are a very good choice, but they come with trade‑offs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JSON‑heavy definitions&lt;/li&gt;
&lt;li&gt;state management between steps&lt;/li&gt;
&lt;li&gt;mental context switching between states&lt;/li&gt;
&lt;li&gt;orchestration logic separated from business logic (this could be a pro, but we should see the use case)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;Lambda Durable Functions&lt;/code&gt; flip the model:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;You write normal async code in just one function and AWS handles durability.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With a single Lambda you can get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;long-running executions (without losing state)&lt;/li&gt;
&lt;li&gt;automatic checkpointing&lt;/li&gt;
&lt;li&gt;deterministic replay&lt;/li&gt;
&lt;li&gt;parallel fan‑out / fan‑in&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;For LLM workflows, where latency, retries, partial failures, and cost control matter, this is huge.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here was my architecture map before starting, the core is an LLM workflow which should implement LLM-as-judge pattern. Having a solution to be placed all in the &lt;code&gt;Durable Lambda&lt;/code&gt;, while front-end act just as a client, is a big milestone to cut off time to production.&lt;/p&gt;

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

&lt;p&gt;How would this look if implemented with &lt;code&gt;AWS Step Functions&lt;/code&gt;?&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Lambda&lt;/th&gt;
&lt;th&gt;State&lt;/th&gt;
&lt;th&gt;Task&lt;/th&gt;
&lt;th&gt;Extra elements required&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Lambda 1&lt;/td&gt;
&lt;td&gt;State 1&lt;/td&gt;
&lt;td&gt;Call LLM&lt;/td&gt;
&lt;td&gt;Must be separate; JSON definition required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lambda 2&lt;/td&gt;
&lt;td&gt;State 2&lt;/td&gt;
&lt;td&gt;Process response&lt;/td&gt;
&lt;td&gt;Another separate Lambda or branch logic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lambda 3&lt;/td&gt;
&lt;td&gt;State 3&lt;/td&gt;
&lt;td&gt;Write to DB&lt;/td&gt;
&lt;td&gt;Separate Lambda&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lambda 4&lt;/td&gt;
&lt;td&gt;State 4&lt;/td&gt;
&lt;td&gt;Map / Parallel&lt;/td&gt;
&lt;td&gt;For fan-out of multiple LLM calls&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lambda 5&lt;/td&gt;
&lt;td&gt;State 5&lt;/td&gt;
&lt;td&gt;Wait / Choice&lt;/td&gt;
&lt;td&gt;For retry / fallback logic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lambda 6&lt;/td&gt;
&lt;td&gt;State 6&lt;/td&gt;
&lt;td&gt;Aggregate results&lt;/td&gt;
&lt;td&gt;Another separate Lambda&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lambda 7&lt;/td&gt;
&lt;td&gt;State 7&lt;/td&gt;
&lt;td&gt;Success / Fail&lt;/td&gt;
&lt;td&gt;Final orchestration state&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Using &lt;code&gt;AWS Step Functions&lt;/code&gt; would require a far larger amount of architectural and logical code compared to a single &lt;code&gt;Lambda Durable Function&lt;/code&gt;. &lt;strong&gt;It’s a huge time saver, eliminates constant context-switching, and reinforces a DDD-inspired approach where my Lambda acts as a fully responsible micro-service, handling parallel execution and the orchestration of results end-to-end.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I always embrace DDD when it makes sense, and I stick to KISS: keep the model focused, the boundaries explicit, and the moving parts to the absolute minimum.&lt;/p&gt;

&lt;p&gt;If you’re looking for a solid framework to choose between the two options, &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/durable-step-functions.html#durable-sfn-decision-framework" rel="noopener noreferrer"&gt;there’s an excellent decision framework here&lt;/a&gt;. Moreover, as suggested in the hybrid architecture chapter, you may even benefit from applying both approaches in your application.&lt;/p&gt;
&lt;h2&gt;
  
  
  ⚖️ LLM‑as‑a‑Judge
&lt;/h2&gt;

&lt;p&gt;So, as written before, instead of trusting a single model output, Nevermore.dev uses this powerful pattern:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Generate candidates&lt;/strong&gt; post-mortem using multiple fast/cheap models&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Judge them&lt;/strong&gt; using a more capable reasoning model&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This, compared with a single model response, gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;better quality&lt;/li&gt;
&lt;li&gt;more consistency&lt;/li&gt;
&lt;li&gt;controllable cost&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In my case, I'm using three models of the &lt;code&gt;Nova&lt;/code&gt; family:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MODEL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;SMALL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;eu.amazon.nova-micro-v1:0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;MEDIUM&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;eu.amazon.nova-lite-v1:0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;LARGE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;eu.amazon.nova-pro-v1:0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🛠️ Deploying Durable Function with CDK
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Durable functions&lt;/code&gt;, per documentation, are still officially not supported by Amplify itself but a &lt;a href="https://github.com/aws-amplify/amplify-backend/pull/3069" rel="noopener noreferrer"&gt;PR&lt;/a&gt; has been &lt;a href="https://github.com/aws-amplify/amplify-backend/releases/tag/%40aws-amplify%2Fbackend%401.20.0" rel="noopener noreferrer"&gt;merged&lt;/a&gt; and I expect this soon to come. &lt;/p&gt;

&lt;p&gt;Meanwhile, it's very simple to deploy a durable function with &lt;code&gt;CDK&lt;/code&gt; (or other IaC tools). It also enforce to me the concept that the durable function is a specific component which should be decoupled by "app architecture" created with &lt;code&gt;Amplify&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It’s mostly a matter of configuration of the &lt;code&gt;Durable Lambda Function&lt;/code&gt; itself.&lt;br&gt;
&lt;strong&gt;And it feels exactly how it should do: an extension of what we already are able to do with CDK.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;durableFunction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DurableFunction&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODEJS_22_X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;index.handler&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Code&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromAsset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lambda&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;functionName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nevermore-dev-durable-ai-generator&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;memorySize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cfnFunction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;durableFunction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultChild&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CfnFunction&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;cfnFunction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;durableConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;executionTimeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hours&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="nf"&gt;toSeconds&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Giving it the right permissions (security first!):&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="nx"&gt;durableFunction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addToRolePolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;iam&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PolicyStatement&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lambda:CheckpointDurableExecution&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lambda:GetDurableExecutionState&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;//better restrict this permission!&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/durable-security.html" rel="noopener noreferrer"&gt;Be aware to restrict this permission resources attribute as suggested  here &lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I've used CDK as it's a good fit for a full Typescript project with &lt;code&gt;AWS Amplify Gen 2&lt;/code&gt; and &lt;code&gt;React&lt;/code&gt; but you can choose and learn how to deploy with your preferred IaC method (Cloudformation, CDK or SAM) a durable function &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/durable-getting-started-iac.html" rel="noopener noreferrer"&gt;here in AWS docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you’re using CDK to deploy your &lt;code&gt;Lambda Durable Function&lt;/code&gt;, you should create a "proxy" function that acts as a backend to invoke it. The code is as simple as &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/durable-invoking.html" rel="noopener noreferrer"&gt;described here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  ✍️ Writing the durable handler
&lt;/h2&gt;

&lt;p&gt;Again, this is the core part: no state machines, no glue code.&lt;br&gt;
&lt;strong&gt;Just code logic.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;withDurableExecution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DurableContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;originalText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;theme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;addams&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;originalText&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&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="nf"&gt;getEmptyContextMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;enhancementPrompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createEnhancementPrompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;originalText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fieldType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;candidates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;generateCandidates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;MODEL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SMALL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;MODEL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MEDIUM&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="nx"&gt;enhancementPrompt&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;judgePrompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createJudgePrompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;originalText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;candidates&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fieldType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;judgment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;judgeAndSelectBest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;MODEL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PRO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;judgePrompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;candidates&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;originalText&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;judgment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;enhancedText&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Parallelism is implemented with a very simple &lt;code&gt;context.map&lt;/code&gt;&lt;br&gt;
Parallel, checkpointed, resumable. Exactly what flaky LLM calls need.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;candidateResults&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Generate enhanced versions&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;models&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;modelId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;enhancement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;converse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;modelId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;modelId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;enhancement&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Judging is implemented as a subsequent durable step&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;judge-best-version&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;judgeResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;converse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;judgeModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;judgePrompt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If parsing fails, I fall back gracefully.&lt;br&gt;
No wasted inference. No duplicate cost.&lt;/p&gt;

&lt;p&gt;This complete pattern code is available &lt;a href="https://github.com/aws-samples/sample-ai-workflows-in-aws-lambda-durable-functions/blob/main/typescript/src/llm-as-judge.ts" rel="noopener noreferrer"&gt;here in aws samples repo&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  ✍️ Prompting
&lt;/h2&gt;

&lt;p&gt;The best thing about this stack is that once the pattern is implemented, you can easily reuse it across different use cases by simply adapting the prompts. Below are my (very simple) examples.&lt;/p&gt;

&lt;p&gt;This one is for generating candidate responses (where &lt;code&gt;fieldName&lt;/code&gt; is the name of the field I want to generate, e.g. description or root cause, and the &lt;code&gt;originalText&lt;/code&gt; is the starting point).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You are an experienced SRE reviewing a technical post-mortem {fieldName}.
Your task is to enhance this {fieldName} with professional insights and technical depth.

Original {fieldName}:
"""
{originalText}
"""

Requirements:
1. Expand and enhance the technical details with clarity and precision
2. Add relevant technical insights, metrics, and potential implications
3. Maintain a professional, clear, and concise tone
4. Use markdown formatting for better readability (headers, lists, code blocks)
5. Focus on actionable insights and lessons learned
6. If the original is empty or minimal, generate a comprehensive {fieldName} based on the context
7. Length: 200-400 words
8. Include specific technical recommendations and next steps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the prompt for the judge&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You are an experienced SRE Lead reviewing post-mortem documents for quality and accuracy.

Original {fieldName}:
"""
{originalText}
"""

Enhanced Versions:
{candidatesList}

Evaluate each version based on:
- Technical accuracy and depth
- Clarity and readability
- Appropriate use of markdown formatting
- Professional tone and structure
- Actionable insights and recommendations
- Completeness and thoroughness

Reply with JSON only (no other text):
{
  "bestIndex": &amp;lt;1-based index&amp;gt;,
  "reasoning": "&amp;lt;2-3 sentences explaining your choice&amp;gt;"
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The interest part of this prompt is that even if i needed just the best response, I've tracked also 2 or 3 sentences explaining the choice. This could be useful to review the result if you wan to introduce a human in the loop with a notification review patter for which &lt;code&gt;Durable functions&lt;/code&gt; are a good fit too (&lt;a href="https://github.com/aws-samples/sample-ai-workflows-in-aws-lambda-durable-functions/blob/main/typescript/src/human-review.ts" rel="noopener noreferrer"&gt;see this example&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;We can also observe that the LLM-as-judge pattern is essentially a composition of other patterns: &lt;a href="https://github.com/aws-samples/sample-ai-workflows-in-aws-lambda-durable-functions/blob/main/typescript/src/parallel-invocation.ts" rel="noopener noreferrer"&gt;parallelism&lt;/a&gt; and &lt;a href="https://github.com/aws-samples/sample-ai-workflows-in-aws-lambda-durable-functions/blob/main/typescript/src/prompt-chaining.ts" rel="noopener noreferrer"&gt;prompt chain&lt;/a&gt; with &lt;a href="https://github.com/aws-samples/sample-ai-workflows-in-aws-lambda-durable-functions/blob/main/typescript/src/structured-output.ts" rel="noopener noreferrer"&gt;structured output&lt;/a&gt;.&lt;br&gt;
By combining these patterns, you gain the flexibility to tailor the solution more precisely to your specific use case.&lt;/p&gt;

&lt;p&gt;The creepy Addams theme give me the opportunity to test that just changing the prompt you can get the custom tone needed or fit your use case.&lt;/p&gt;

&lt;p&gt;Here is the adapted prompt for candidate responses&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You are Grandmama Addams, an ancient and wise debugger from the Addams Family mansion. 
Your task is to enhance this technical post-mortem {fieldName} with your dark wisdom and supernatural insight.

Original {fieldName}:
"""
{originalText}
"""

Requirements:
1. Expand and enhance the technical details with clarity and depth
2. Add relevant technical insights and potential implications
3. Maintain an Addams Family tone - creepy, darkly humorous, but technically accurate
4. Use markdown formatting for better readability (headers, lists, code blocks)
5. Keep it professional yet delightfully macabre
6. If the original is empty or minimal, generate a comprehensive {fieldName} based on the context
7. Length: 200-400 words
8. Include specific technical recommendations
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is the adapted prompt for the judge&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You are Morticia Addams, reviewing post-mortem documents for quality and accuracy.

Original {fieldName}:
"""
{originalText}
"""

Enhanced Versions:
{candidatesList}

Evaluate each version based on:
- Technical accuracy and depth
- Clarity and readability
- Appropriate use of markdown formatting
- Addams Family tone while remaining professional
- Actionable insights and recommendations

Reply with JSON only (no other text):
{
  "bestIndex": &amp;lt;1-based index&amp;gt;,
  "reasoning": "&amp;lt;2-3 sentences explaining your choice&amp;gt;"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia1.giphy.com%2Fmedia%2Fv1.Y2lkPTc5MGI3NjExbHBxdTYwYzluY2Z2NHozNDlyNXdydGd3bjc2aHMzNTRiNzVwbjQ1MSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw%2FkUS0uhiMyGqLfyN6pr%2Fgiphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia1.giphy.com%2Fmedia%2Fv1.Y2lkPTc5MGI3NjExbHBxdTYwYzluY2Z2NHozNDlyNXdydGd3bjc2aHMzNTRiNzVwbjQ1MSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw%2FkUS0uhiMyGqLfyN6pr%2Fgiphy.gif" width="500" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I picked Morticia as the judge because her personality fits the role beautifully, but it was extremely funny to see how dramatically the tone changed just by switching to another member of the Addams family (choosing Fester to add a touch of madness was absolutely absurd).&lt;/p&gt;

&lt;h2&gt;
  
  
  👀 Let's see it in action
&lt;/h2&gt;

&lt;p&gt;When invoking the function we can see the execution in &lt;code&gt;Lambda&lt;/code&gt; Console under brand new &lt;code&gt;Durable Executions&lt;/code&gt; tab.&lt;/p&gt;

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

&lt;p&gt;You have a high level of detail at every step&lt;/p&gt;

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

&lt;h2&gt;
  
  
  🎯 So, why this matters?
&lt;/h2&gt;

&lt;p&gt;Durable Functions make Lambda viable for &lt;strong&gt;serious AI workflows&lt;/strong&gt; without needing a &lt;code&gt;Step Function&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;multi‑step reasoning&lt;/li&gt;
&lt;li&gt;fan‑out / fan‑in&lt;/li&gt;
&lt;li&gt;partial failures&lt;/li&gt;
&lt;li&gt;cost‑aware retries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In my use case: post‑mortems are still painful.&lt;br&gt;
But now, at least, they’re &lt;em&gt;elegantly painful&lt;/em&gt;, ai-assisted with the generation and judge of this generation in a single scoped micro-service without an external workflow handler tool.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia0.giphy.com%2Fmedia%2Fv1.Y2lkPTc5MGI3NjExaG1xemgwajdpajUwaHVkMzVud2E0cnByY25nbHU2cnFobW9lb2J1bSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw%2FB4L7i0Kuhi5b2%2Fgiphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia0.giphy.com%2Fmedia%2Fv1.Y2lkPTc5MGI3NjExaG1xemgwajdpajUwaHVkMzVud2E0cnByY25nbHU2cnFobW9lb2J1bSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw%2FB4L7i0Kuhi5b2%2Fgiphy.gif" width="500" height="429"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/aws-samples/sample-ai-workflows-in-aws-lambda-durable-functions" rel="noopener noreferrer"&gt;Sample ai workflows in AWS Lambda Durable Functions repo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/durable-getting-started-iac.html" rel="noopener noreferrer"&gt;Deploy Lambda durable functions with Infrastructure as Code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/durable-security.html" rel="noopener noreferrer"&gt;Security and permissions for Lambda durable functions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/durable-invoking.html" rel="noopener noreferrer"&gt;Invoking durable Lambda functions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/durable-step-functions.html" rel="noopener noreferrer"&gt;Durable functions or Step functions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🙋 Who am I
&lt;/h2&gt;

&lt;p&gt;I'm &lt;a href="https://www.linkedin.com/in/desiodavide" rel="noopener noreferrer"&gt;D. De Sio&lt;/a&gt; and I work as a Head of Software Engineering in &lt;a href="https://eleva.it/" rel="noopener noreferrer"&gt;Eleva&lt;/a&gt;.&lt;br&gt;
I'm currently (Apr 2025) an &lt;a href="https://www.credly.com/badges/9929fdf2-7a3d-4013-9de6-57c80e4920b9/public_url" rel="noopener noreferrer"&gt;AWS Certified Solution Architect Professional&lt;/a&gt; and &lt;a href="https://www.credly.com/badges/8c5a1487-191b-429e-8c2d-7cee43bf316b/public_url" rel="noopener noreferrer"&gt;AWS Certified DevOps Engineer Professional&lt;/a&gt;, but also a &lt;a href="https://www.linkedin.com/company/aws-user-group-pavia/" rel="noopener noreferrer"&gt;User Group Leader (in Pavia)&lt;/a&gt;, an &lt;strong&gt;AWS Community Builder&lt;/strong&gt; and, last but not least, a #serverless enthusiast.&lt;/p&gt;

&lt;p&gt;For the occasion, I proudly count myself among the students of &lt;a href="https://www.nevermoreacademy.com/" rel="noopener noreferrer"&gt;Nevermore Academy&lt;/a&gt; for outcasts.&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%2F8r2qfatbmmjoadteol48.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%2F8r2qfatbmmjoadteol48.jpg" alt=" " width="800" height="1143"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>genai</category>
      <category>lambda</category>
    </item>
    <item>
      <title>Building a Kiro Power for AWS Amplify Gen 2</title>
      <dc:creator>Davide De Sio</dc:creator>
      <pubDate>Fri, 09 Jan 2026 09:44:10 +0000</pubDate>
      <link>https://dev.to/aws-builders/building-a-kiro-power-for-aws-amplify-gen-2-54gk</link>
      <guid>https://dev.to/aws-builders/building-a-kiro-power-for-aws-amplify-gen-2-54gk</guid>
      <description>&lt;h2&gt;
  
  
  🏃 TL;DR
&lt;/h2&gt;

&lt;p&gt;There’s a moment that often comes after big conferences.&lt;br&gt;
A brief pause, when the excitement fades and only the right questions remain.&lt;/p&gt;

&lt;p&gt;For me, that moment arrived after the latest AWS re:Invent in December, with the announcement of &lt;a href="https://kiro.dev/powers/" rel="noopener noreferrer"&gt;Kiro Powers&lt;/a&gt; and then almost by accident, when I stumbled upon a brand-new page in the AWS Amplify Gen 2 documentation: &lt;a href="https://docs.amplify.aws/react/start/mcp-server/" rel="noopener noreferrer"&gt;Build with AI assistants&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It made me ask a simple question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;What if working with &lt;code&gt;Amplify Gen 2&lt;/code&gt; could feel more guided, more intentional, and less repetitive, every single time?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That question eventually became &lt;a href="https://github.com/davide-desio-eleva/amplify-gen2-kiro-power" rel="noopener noreferrer"&gt;AWS Amplify Gen 2 Kiro Power&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  ✨ AWS MCP server and AWS SOPs
&lt;/h2&gt;

&lt;p&gt;I immediately started experimenting &lt;a href="https://docs.aws.amazon.com/aws-mcp/latest/userguide/what-is-mcp-server.html" rel="noopener noreferrer"&gt;AWS MCP SOPs&lt;/a&gt; integration for &lt;code&gt;AWS Amplify Gen 2&lt;/code&gt;, as prompts and guidance rules as suggested by documentation. I tried it in a few real scenarios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Building a &lt;strong&gt;full application from scratch&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Adding a &lt;strong&gt;new backend&lt;/strong&gt; to an existing frontend&lt;/li&gt;
&lt;li&gt;Creating a &lt;strong&gt;frontend&lt;/strong&gt; for a project where only the backend existed&lt;/li&gt;
&lt;li&gt;Being guided step by step through &lt;strong&gt;deployment and configuration&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What surprised me wasn’t just that it worked, it was &lt;em&gt;how well it worked&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The agent didn’t just execute commands: it followed patterns, respected best practices and reduced the mental overhead of remembering &lt;em&gt;how&lt;/em&gt; &lt;code&gt;AWS Amplify Gen 2&lt;/code&gt; wants things done.&lt;/p&gt;

&lt;p&gt;But what I finally wanted to achieve was not to use prompts, but for the agent to be able to guide me autonomously. &lt;/p&gt;

&lt;p&gt;At this point this was my new question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Why am I loading MCP SOPs upfront for every request, when the agent could just “know” when to use it dinamically?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  👻 From idea to a Kiro power
&lt;/h2&gt;

&lt;p&gt;Instead of treating &lt;code&gt;AWS MCP SOPs&lt;/code&gt; as something external plugged into the agent and loaded upfront, I wanted the agent to &lt;strong&gt;know when activate it and use it&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That’s where &lt;strong&gt;Kiro Power&lt;/strong&gt; comes in.&lt;/p&gt;

&lt;p&gt;Traditional MCP servers are loaded upfront, while &lt;strong&gt;a power enables Dynamic MCP tool loading saving context (and thus tokens!)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The idea is simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Let the agent know how &lt;strong&gt;Amplify Gen 2 actually works&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Encode best practices, workflows, and conventions&lt;/li&gt;
&lt;li&gt;Make those rules automatically available whenever &lt;code&gt;AWS Amplify&lt;/code&gt; is part of the conversation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So every time the agent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Designs a backend with &lt;code&gt;AWS Amplify&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Modifies an existing &lt;code&gt;AWS Amplify&lt;/code&gt; project&lt;/li&gt;
&lt;li&gt;Generates frontend code for an &lt;code&gt;AWS Amplify&lt;/code&gt; app&lt;/li&gt;
&lt;li&gt;Handles environment setup or deployment via &lt;code&gt;AWS Amplify&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;it does so without loading the &lt;code&gt;AWS MCP Server&lt;/code&gt; upfront but having &lt;code&gt;AWS Amplify Gen 2&lt;/code&gt; &lt;em&gt;in mind&lt;/em&gt; and knowing when &lt;em&gt;activate&lt;/em&gt; the power, without me having to restate the rules every time i need it.&lt;/p&gt;

&lt;h2&gt;
  
  
  📦 What I've build.
&lt;/h2&gt;

&lt;p&gt;I started by following the official instructions to create a Kiro Power, which you can find &lt;a href="https://kiro.dev/docs/powers/create/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That’s when I realized something amusing:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;There is a power to create powers.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So I installed it and let it guide me in building my own, a personal Kiro Power tailored specifically for &lt;code&gt;AWS Amplify Gen 2&lt;/code&gt;.&lt;/p&gt;

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

&lt;p&gt;From there, it became an iterative process: I reviewed the generated output, tightened the rules, explicitly blocked &lt;code&gt;AWS Amplify Gen 1&lt;/code&gt; related commands, and added behaviors based on my hands-on experience with &lt;code&gt;AWS Amplify Gen 2&lt;/code&gt; in real projects.&lt;/p&gt;

&lt;p&gt;The final repository contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Kiro Power definition focused on &lt;code&gt;AWS Amplify Gen 2&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Embedded &lt;code&gt;AWS MCP SOPs&lt;/code&gt; that guide architecture, setup, and evolution&lt;/li&gt;
&lt;li&gt;A structure designed to be reusable and extensible&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;It’s not meant to replace documentation but to operationalize it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You can find the full implementation and details here:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/davide-desio-eleva/amplify-gen2-kiro-power" rel="noopener noreferrer"&gt;AWS Amplify Gen 2 Kiro Power&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🤝 Contributing
&lt;/h2&gt;

&lt;p&gt;Also I've made a &lt;a href="https://github.com/kirodotdev/powers/pull/43/commits" rel="noopener noreferrer"&gt;PR&lt;/a&gt; to the official &lt;a href="https://github.com/kirodotdev/powers" rel="noopener noreferrer"&gt;kirodotdev/powers&lt;/a&gt; repo hoping this will be merged for all folks out there building with &lt;code&gt;AWS Amplify Gen 2&lt;/code&gt;. You can use also &lt;a href="https://github.com/davide-desio-eleva/powers" rel="noopener noreferrer"&gt;this repo&lt;/a&gt; if you want to try all powers officially available but including mine.&lt;/p&gt;

&lt;h2&gt;
  
  
  👀 See it in action
&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%2Fjrr6wod69xbyp33ot99v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjrr6wod69xbyp33ot99v.png" alt=" " width="800" height="337"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  After Installation
&lt;/h3&gt;

&lt;p&gt;Once the power is installed, Kiro will show you a confirmation and overview of what the power provides:&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Power Usage Guide
&lt;/h3&gt;

&lt;p&gt;When you ask Kiro for help with Amplify Gen 2, it will propose the available workflows and guide you through the process:&lt;/p&gt;

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

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

&lt;p&gt;&lt;code&gt;AWS Amplify Gen 2&lt;/code&gt; is powerful, but it also introduces new mental models:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Backend-first thinking&lt;/li&gt;
&lt;li&gt;Strong conventions&lt;/li&gt;
&lt;li&gt;Opinionated workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those are great, until you context-switch, forget a detail, or come back to a project weeks later.&lt;/p&gt;

&lt;p&gt;Also there is still a lot of confusion with &lt;code&gt;AWS Amplify Gen 1&lt;/code&gt; doc and samples (at least for me) and folks migrating from Gen 1 projects can easily feel overwhelmed (I’ve definitely felt that pain!).&lt;br&gt;
&lt;strong&gt;You can test this yourself: ask Kiro to initialize a project without mentioning the generation. It will default to Gen1, or worse, it may switch back and forth between Gen 1 and Gen 2 as you iterate.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;By encoding Gen 2 guidance via &lt;code&gt;AWS MCP SOPs&lt;/code&gt; into Kiro agent with a power:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You reduce cognitive load&lt;/li&gt;
&lt;li&gt;You avoid subtle mistakes between generation&lt;/li&gt;
&lt;li&gt;You keep architectural decisions consistent over time&lt;/li&gt;
&lt;li&gt;You use best practices&lt;/li&gt;
&lt;li&gt;You use a security first approach&lt;/li&gt;
&lt;li&gt;You don't waste tokens (and money) when you're not speaking about Amplify!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In short, you let Kiro agent worry about &lt;em&gt;remembering AWS Amplify Gen 2 doc and best practices&lt;/em&gt;, so you can focus on &lt;em&gt;building your app&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  🙏 Acknowledgements
&lt;/h2&gt;

&lt;p&gt;This work wouldn’t be the same without thoughtful feedback and sharp reviews.&lt;/p&gt;

&lt;p&gt;A big shout-out to &lt;strong&gt;&lt;a href="https://www.linkedin.com/in/catalinborsan/" rel="noopener noreferrer"&gt;Catalin Borsan&lt;/a&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;a href="https://www.linkedin.com/in/frabertani/" rel="noopener noreferrer"&gt;Francesco Bertani&lt;/a&gt;&lt;/strong&gt;: their input helped shape this from an experiment into something actually useful.&lt;/p&gt;

&lt;h2&gt;
  
  
  🙋 Who am I
&lt;/h2&gt;

&lt;p&gt;I'm &lt;a href="https://www.linkedin.com/in/desiodavide" rel="noopener noreferrer"&gt;D. De Sio&lt;/a&gt; and I work as a Head of Software Engineering in &lt;a href="https://eleva.it/" rel="noopener noreferrer"&gt;Eleva&lt;/a&gt;.&lt;br&gt;
I'm currently (Apr 2025) an &lt;a href="https://www.credly.com/badges/9929fdf2-7a3d-4013-9de6-57c80e4920b9/public_url" rel="noopener noreferrer"&gt;AWS Certified Solution Architect Professional&lt;/a&gt; and &lt;a href="https://www.credly.com/badges/8c5a1487-191b-429e-8c2d-7cee43bf316b/public_url" rel="noopener noreferrer"&gt;AWS Certified DevOps Engineer Professional&lt;/a&gt;, but also a &lt;a href="https://www.linkedin.com/company/aws-user-group-pavia/" rel="noopener noreferrer"&gt;User Group Leader (in Pavia)&lt;/a&gt;, an &lt;strong&gt;AWS Community Builder&lt;/strong&gt; and, last but not least, a #serverless enthusiast.&lt;/p&gt;

&lt;p&gt;My work in this field is to advocate about serverless and help as more dev teams to adopt it, as well as customers break their monolith into API and micro-services using it.&lt;/p&gt;

</description>
      <category>kiro</category>
      <category>aws</category>
      <category>amplify</category>
    </item>
    <item>
      <title>2025 Wrapped: still building, sharing, and finding my place in the community</title>
      <dc:creator>Davide De Sio</dc:creator>
      <pubDate>Mon, 29 Dec 2025 09:33:57 +0000</pubDate>
      <link>https://dev.to/aws-builders/2025-wrapped-still-building-sharing-and-finding-my-place-in-the-community-3bme</link>
      <guid>https://dev.to/aws-builders/2025-wrapped-still-building-sharing-and-finding-my-place-in-the-community-3bme</guid>
      <description>&lt;p&gt;Every year I try to set myself a simple goal: build things that are useful, write about what I learn, and show up for the community.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This year, I've set a metric: at least 12 meetups as AWS user group leader or member. I've failed it, but that goal turned into something much bigger.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🏃 TL;DR
&lt;/h2&gt;

&lt;p&gt;I tried to measure a year with numbers.&lt;/p&gt;

&lt;p&gt;This is a story about setting goals, missing some of them, and accidentally building something much bigger in the process. It’s about communities that start from zero chairs and end up full of conversations, about writing that turns into thinking, and thinking that only works because someone on the other side is paying attention.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No grand finale. This is my last article of the year: the only one without code, diagrams, or architectures, but maybe one that mattered most to me.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🔗 Starting from the ground up: AWS User Groups
&lt;/h2&gt;

&lt;p&gt;One of the most meaningful challenges this year was founding a new &lt;strong&gt;&lt;a href="https://www.awsugcuneo.it/" rel="noopener noreferrer"&gt;AWS User Group in Cuneo&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;Starting a UG from scratch is very different from joining an established one. There’s no audience, no routine, no guarantees. You need to convince people that showing up is worth their time, that there is value in sharing experiences even when things are still rough around the edges. &lt;/p&gt;

&lt;p&gt;Everything has felt easier thanks to &lt;a href="https://www.linkedin.com/in/leonardoviada/" rel="noopener noreferrer"&gt;Leonardo Viada&lt;/a&gt; and &lt;a href="https://www.linkedin.com/in/gioeleblanc/" rel="noopener noreferrer"&gt;Gioele Blanc&lt;/a&gt; as partners in crime, but also because people have responded with genuine energy and curiosity.&lt;/p&gt;

&lt;p&gt;A special thanks goes to &lt;a href="https://www.linkedin.com/in/alessandroponzo/" rel="noopener noreferrer"&gt;Alessandro Ponzo&lt;/a&gt;, who acted as our sponsor and mentor, supporting every meetup: not only through his talks, but by actively guiding and nurturing the community.&lt;/p&gt;

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

&lt;p&gt;Seeing the first meetup come to life, with real conversations, real questions, and real enthusiasm, made all the effort worth it. It confirmed something I strongly believe in: strong communities don’t start with stages or sponsors, they start with trust and curiosity.&lt;/p&gt;

&lt;p&gt;In parallel, &lt;strong&gt;AWS User Group Pavia&lt;/strong&gt; kept being a good playground for experimentation. We started in 2024 and this year we pushed things further with &lt;strong&gt;5 meetups and a developer challenge&lt;/strong&gt;, getting hands-on with &lt;strong&gt;Amazon Q Developer&lt;/strong&gt;, with the huge help of &lt;a href="https://www.linkedin.com/in/catalinborsan/" rel="noopener noreferrer"&gt;Catalin Borsan&lt;/a&gt; and &lt;a href="https://www.linkedin.com/in/frabertani/" rel="noopener noreferrer"&gt;Francesco Bertani&lt;/a&gt;, and turning learning into something tangible and fun. Watching people build, compete, and collaborate reminded me why UG formats work so well when they are practical and inclusive. &lt;/p&gt;

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

&lt;p&gt;We also experimented with new formats, such as the “re:Cap AWS Milan Summit 2025”, conceived as a counterpoint to the traditional “re:Cap re:Invent”. In this format, we explored the key announcements and most relevant moments from Italy’s main AWS-focused event, following the same approach traditionally used in December for AWS’s most important global conference.&lt;/p&gt;

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

&lt;p&gt;Here, organizing and experimenting felt effortless thanks to the community superheroes from Pavia and beSharp: &lt;a href="https://www.linkedin.com/in/lucaballista/" rel="noopener noreferrer"&gt;Luca Ballista&lt;/a&gt;, &lt;a href="https://www.linkedin.com/in/damiano-giorgi/" rel="noopener noreferrer"&gt;Damiano Giorgi&lt;/a&gt; and &lt;a href="https://www.linkedin.com/in/a-callegari/" rel="noopener noreferrer"&gt;Antonio Callegari&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;AWS User Groups were the constant thread throughout the year: not just events, but places where ideas are tested before becoming blog posts, talks, or projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  🇮🇹 From local to national: being part of the Italian community’s voice and Community Days
&lt;/h2&gt;

&lt;p&gt;I'll start from this: I had the honor of representing AWS User Group Pavia during the live streaming of the re:Invent CEO Keynote for the AWS UG Italy community.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Being invited was already meaningful.&lt;br&gt;
Meeting such expert people from other cities and AWS User Groups made it even better.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Those conversations made one thing clear: there’s a lot of energy moving inside the Italian AWS community, and many shared ideas that could turn into great collaborations in 2026. Let’s just say some of them might involve familiar faces, we’ll see what happens &lt;a href="https://www.linkedin.com/in/andysal/" rel="noopener noreferrer"&gt;Andrea Saltarello&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The same energy was clear to me when Italian AWS community deliver a strong, unified response to &lt;a href="https://www.linkedin.com/in/michal-salanci-%F0%9F%AB%90-3496724b/" rel="noopener noreferrer"&gt;Michal Salanci&lt;/a&gt; 's exciting initiative, AWS Community pre:Invent Warmup, giving participants the chance to win a trip to re:Invent in Las Vegas.&lt;/p&gt;

&lt;p&gt;I personally took the opportunity to amplify the message, helping as many Italian UG as possible get involved as it was a wonderful opportunity for Italian UG members to win a very big prize (going to Vegas)!&lt;/p&gt;

&lt;p&gt;In Italy, August is sacred: everyone disappears on vacation, so I was definitely not expecting much engagement. &lt;strong&gt;Instead, the response was immediate: posts across Italian AWS User Groups, emails through Meetup, and genuine enthusiasm to share this opportunity with everyone. Italy became the national community with the largest representation among AWS UG partners in this initiative&lt;/strong&gt;!&lt;/p&gt;

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

&lt;p&gt;I’d like to give a big shout-out and heartfelt thanks to the to AWS UG leaders across cities &lt;a href="https://www.linkedin.com/in/simonemerlini/" rel="noopener noreferrer"&gt;Simone Merlini&lt;/a&gt;, &lt;a href="https://www.linkedin.com/in/lucaballista/" rel="noopener noreferrer"&gt;Luca Ballista&lt;/a&gt;, &lt;a href="https://www.linkedin.com/in/guidonebiolo/" rel="noopener noreferrer"&gt;Guido Maria Nebiolo&lt;/a&gt;, &lt;a href="https://www.linkedin.com/in/leonardoviada/" rel="noopener noreferrer"&gt;Leonardo Viada&lt;/a&gt;, &lt;a href="https://www.linkedin.com/in/immacolata-smelzo-97959072/" rel="noopener noreferrer"&gt;Immacolata Smelzo&lt;/a&gt; and &lt;a href="https://www.linkedin.com/in/monicacolangelo/" rel="noopener noreferrer"&gt;Monica Colangelo&lt;/a&gt; whose energy made this possible.&lt;/p&gt;

&lt;p&gt;And finally this energy scaled up beautifully at the &lt;strong&gt;AWS Community Day Italy&lt;/strong&gt;: I've been here only to partecipate, just as a member of the community and not as an organizer.&lt;/p&gt;

&lt;p&gt;At this point, it feels to me less like an event and more like a reunion of experts and friends, all coming from AWS initiatives and AWS User Groups. You don’t need external validation anymore, the room itself is proof of how much skill, passion, and experimentation is happening in the Italian cloud community.&lt;/p&gt;

&lt;p&gt;One message that really stuck with me was shared by &lt;a href="https://www.linkedin.com/in/rlosio/" rel="noopener noreferrer"&gt;Renato Losio&lt;/a&gt; and it should probably be an aspiration for anyone working in tech.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  🚀 AWS Community Builders: the multiplier effect
&lt;/h2&gt;

&lt;p&gt;Yet this year, something truly special happened.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;I officially joined the AWS Community Builders program&lt;/strong&gt;, in the Serverless category.&lt;/p&gt;

&lt;p&gt;And suddenly I feel &lt;strong&gt;more responsibility to give back&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Being part of this program didn’t change what I do day to day. I was already writing, building, and playing with real-world use cases. &lt;strong&gt;What changed was the amplification opportunity.&lt;/strong&gt; supported from people like &lt;a href="https://www.linkedin.com/in/jasonrobertdunn/" rel="noopener noreferrer"&gt;Jason Dunn&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This program became the foundation that amplified everything else I've made this year: articles, talks, experiments, and community work.&lt;/p&gt;

&lt;p&gt;You also get rewarded for good content, as happened to me after joining the program and giving me the opportunity to get some cool swag products.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  ✍️ When writing starts to echo back
&lt;/h2&gt;

&lt;p&gt;I have always written my articles in English because I consider it the ideal language to integrate seamlessly with the code being explained, and because I believe that impact scales when knowledge crosses borders. This year, amplified by AWS Community Builders community, that choice paid off in unexpected ways.&lt;/p&gt;

&lt;p&gt;Seeing my articles cited in international newsletters felt surreal at first. &lt;/p&gt;

&lt;p&gt;Being featured multiple times by &lt;a href="https://www.linkedin.com/in/allenheltondev/" rel="noopener noreferrer"&gt;Allen Helton&lt;/a&gt; in &lt;a href="https://www.linkedin.com/company/readysetcloud/" rel="noopener noreferrer"&gt;Ready, Set, Cloud&lt;/a&gt;, by &lt;a href="https://www.linkedin.com/in/lee-james-gilmore/" rel="noopener noreferrer"&gt;Lee Gilmore&lt;/a&gt; in multiple issues of &lt;a href="https://www.serverlessadvocate.com/" rel="noopener noreferrer"&gt;Serverless Advocate&lt;/a&gt;, &lt;strong&gt;&lt;a href="https://serverlessadvocate.substack.com/i/168133236/ask-the-expert" rel="noopener noreferrer"&gt;including being selected as a Serverless expert&lt;/a&gt;&lt;/strong&gt;, and by &lt;a href="https://www.linkedin.com/in/jones-zachariah-noel-n/" rel="noopener noreferrer"&gt;Jones Zachariah Noel N&lt;/a&gt; in &lt;a href="https://blog.theserverlessterminal.com/" rel="noopener noreferrer"&gt;Serverless Terminal&lt;/a&gt;, was a huge honor.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not because of visibility, but because my work appeared cited and next to people I’ve been learning from for years, who were also genuinely open, approachable, and generous with their time and feedback&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;One of my technical articles was also cited in the &lt;strong&gt;Spanish&lt;/strong&gt; newsletter of &lt;a href="https://www.linkedin.com/in/marciavillalba/" rel="noopener noreferrer"&gt;Marcia Villalba&lt;/a&gt;: &lt;a href="https://desplegando.substack.com/i/175186368/construye-un-agente-serverless-con-contexto-persistente-usando-strands-agents-sdk" rel="noopener noreferrer"&gt;desplegando.cloud&lt;/a&gt;. Something I honestly didn’t see coming. Being referenced in English newsletters or Italian communities already feels meaningful, but seeing my work cited in another language adds an entirely different perspective.&lt;/p&gt;

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

&lt;p&gt;At some point, even companies shaping the AI space started referencing my projects. &lt;strong&gt;Seeing work around agent memory highlighted by teams like &lt;a href="https://www.linkedin.com/company/mem0/" rel="noopener noreferrer"&gt;Mem0&lt;/a&gt;&lt;/strong&gt;. &lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;If you’ve ever been part of a real community, you know that&lt;br&gt;
what you give is never comparable to what you get back.&lt;/strong&gt;&lt;br&gt;
This year, I can assure you, what I’ve received in return far exceeds what I’ve given.&lt;/p&gt;

&lt;p&gt;I have also started published content in Italian, &lt;a href="https://www.tomshw.it/business/perche-il-tuo-progetto-ai-fallira-se-non-gli-dai-il-giusto-contesto" rel="noopener noreferrer"&gt;including a recent article on Tom’s Hardware &lt;/a&gt;. I believe that contributing in my native language is equally important, as it allows me to give back to my local community and make knowledge more accessible.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  🧠 From writing to thinking
&lt;/h2&gt;

&lt;p&gt;I’ve come to realize that this feedback loop exists only because I started writing differently.&lt;/p&gt;

&lt;p&gt;At some point, &lt;strong&gt;I stopped treating blog posts as explanations and started using them as a way to reason about systems and architectures&lt;/strong&gt;. Each article became a place to slow down, question my own assumptions, and test whether an idea could survive contact with reality, with experts and could be shipped to production.&lt;/p&gt;

&lt;p&gt;I've worked hard on &lt;strong&gt;&lt;a href="https://dev.to/aws-builders/deploy-your-first-ai-agent-with-strands-agents-sdk-j85"&gt;Strands Agents SDK series&lt;/a&gt;&lt;/strong&gt; which turn from a simple quick start into a deeper exploration of how agents behave in real environments: adding memory in serverless setups, managing context in stateless architectures, introducing guardrails and designing an agent that don’t collapse outside a demo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://dev.to/aws-builders/deploy-a-minimal-mcp-server-on-aws-lambda-with-serverless-framework-3e42"&gt;MCP series&lt;/a&gt;&lt;/strong&gt; followed the same philosophy. Instead of amplifying hype around MCP, I focused on making it deployable and understandable: minimal MCP servers in serverless on AWS Lambda, different IaC frameworks using Serverless Framework, CDK, and SAM, and eventually a small CLI to help choose the right approach based on actual constraints.&lt;/p&gt;

&lt;p&gt;I wrote also a mini &lt;strong&gt;&lt;a href="http://dev.to/ddesio/rag-on-aws-building-an-ai-powerd-knowledge-base-with-amazon-bedrock-and-pinecone-using-aws-nl1"&gt;RAG on AWS series&lt;/a&gt;&lt;/strong&gt;, starting from a solid way to do it with Pinecone to an experimental one with Amazon S3 Vectors which could be anyway shipped to production to reduce costs.&lt;/p&gt;

&lt;p&gt;The common goal was clarity. Turning abstract concepts into something you can deploy, break, observe, and improve.&lt;/p&gt;

&lt;p&gt;That’s where writing stopped being just teaching or showing off something, and became a tool to think better about architecture and to share that thinking with people who were walking the same path (and give me real feedback).&lt;/p&gt;

&lt;h2&gt;
  
  
  🎤 Conferences as convergence points
&lt;/h2&gt;

&lt;p&gt;For the same reason, conferences this year were not just places to listen to empty stories, but spaces where I can find ideas to test, challenge, and ground in reality.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Go Serverless&lt;/strong&gt; was probably the clearest expression of this. An event organized together with the Eleva team, it became a stage for real production stories, where teams talked openly about trade-offs, constraints, and decisions. No polished marketing narratives, just architectures that exist because they solve real business problems. &lt;strong&gt;After three editions, our takeaway is simple: serverless is no longer experimental. It’s a deliberate, strategic choice.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0fd14062cwwic8grmfaf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0fd14062cwwic8grmfaf.png" alt=" " width="800" height="532"&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%2F39g97ltholil6f89cywd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F39g97ltholil6f89cywd.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ServerlessDays Milan&lt;/strong&gt; played a different but equally important role. Joining as a speaker, I experienced firsthand how these events sit in a unique space between conferences and meetups. &lt;/p&gt;

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

&lt;p&gt;They’re where patterns meet people, and where conversations start immediately after the last slide.&lt;/p&gt;

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

&lt;p&gt;Topics discussed online suddenly had faces, voices, and follow-up debates. People you read, quote, or learn from turn into peers you can challenge, agree with, or build alongside. That continuity is what turns isolated content into an ecosystem, and a real community.&lt;/p&gt;

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

&lt;p&gt;The &lt;strong&gt;AWS Summit in Milan&lt;/strong&gt; tied everything together. Not as a single highlight, but as a confirmation. Seeing the Italian community show up, participate in Game Day challenges powered by Amazon Q Developer, and actively occupy the community spaces was a reminder of how much energy there is when builders are given room to connect. Spending the day with the Eleva team made it even clearer that community is not something parallel to work. It’s part of how good work happens.&lt;/p&gt;

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

&lt;p&gt;Across all these events, the pattern stayed the same: ideas move faster when people meet, and your work get better when stories are shared where they can be questioned, reused, and improved.&lt;/p&gt;

&lt;h2&gt;
  
  
  🛠️ GenAI workshops: real needs avoiding the hype
&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%2Ftccm1iehs4ukwf1dkb8l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftccm1iehs4ukwf1dkb8l.png" alt=" " width="800" height="266"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An important part of the year was running GenAI workshops for AWS. I ran quite a few of them this year (at least 10), including one hosted at the AWS office in Milan, with 25 seats filled by people coming from different companies. &lt;/p&gt;

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

&lt;p&gt;I've worked on concrete use cases, real business processes, and scenarios where GenAI can deliver tangible and immediate value. Only after understanding why, we move to the how: models, architectures, Amazon Bedrock, security, and data governance.&lt;/p&gt;

&lt;p&gt;The goal was never to sell, it was to understand where GenAI truly makes sense, and how Eleva and AWS can help organizations adopt it responsibly.&lt;/p&gt;

&lt;p&gt;Because in the end, technology is a tool, not the goal.&lt;br&gt;
It exists to solve real problems, not to create hype.&lt;/p&gt;

&lt;h2&gt;
  
  
  📚 Continuous Learning: helping shaping new certifications
&lt;/h2&gt;

&lt;p&gt;Another highlight of 2025 was being invited by &lt;a href="https://www.linkedin.com/in/pamyoungbrown/" rel="noopener noreferrer"&gt;Pamela Brown&lt;/a&gt; as  from AWS to join the beta program shaping the future of hands-on AWS certifications, focused on serverless and agentic AI. Since I hadn’t planned to pursue any certifications this year, it came as a great surprise and an amazing opportunity to challenge myself.&lt;/p&gt;

&lt;p&gt;These new microcredentials aren’t about memorizing services. They’re about applying knowledge: solving real scenarios, building, debugging, and finding working solutions.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa97xw347st0bse5k2sdd.png" width="600" height="600"&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxqwv08n46hrzzq9awrs4.png" width="600" height="600"&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Being part of the beta wasn’t just about taking exams; it was about contributing feedback to how future builders will learn. The self-paced exam labs were a great refresher, but also a strong reminder that &lt;strong&gt;hands-on first is what truly makes skills stick perfectly aligned with the shift I’ve made this year in my articles and writing, focusing on practical, applicable knowledge rather than just theory.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🔺 A special note on Eleva
&lt;/h2&gt;

&lt;p&gt;None of this would have been possible without &lt;a href="https://www.linkedin.com/company/eleva-srl/" rel="noopener noreferrer"&gt;Eleva&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Not just as a company, but as an environment that genuinely supported this journey day after day. Eleva gave me the space, trust, and encouragement to grow, explore ideas, and invest time in communities, writing, and learning, knowing I was never doing it alone.&lt;/p&gt;

&lt;p&gt;What truly made the difference are the people.&lt;br&gt;
&lt;a href="https://www.linkedin.com/in/lucaformenti/" rel="noopener noreferrer"&gt;Luca&lt;/a&gt;, &lt;a href="https://www.linkedin.com/in/claudiabelloli/" rel="noopener noreferrer"&gt;Claudia&lt;/a&gt;, &lt;a href="https://www.linkedin.com/in/salvatore-russo-s22/" rel="noopener noreferrer"&gt;Salvatore&lt;/a&gt;, &lt;a href="https://www.linkedin.com/in/adriana-rava/" rel="noopener noreferrer"&gt;Adriana&lt;/a&gt; and &lt;a href="https://www.linkedin.com/in/lorenzodefilippi/" rel="noopener noreferrer"&gt;Lorenzo&lt;/a&gt; your constant support, openness, and belief in my growth shaped much of what I was able to achieve this year. Having people who care, who listen, and who actively invest in your development changes everything.&lt;/p&gt;

&lt;p&gt;I also want to take a moment to recognize the developers on my team. Much of their work happens quietly, out of sight, but it’s the solid ground that supports every opportunity described here. This year, in agreement with Lorenzo, I took on the role of &lt;strong&gt;Head of Software Engineering&lt;/strong&gt;, which brought new challenges to my table: &lt;strong&gt;guiding others in their technical and professional growth has been both a joy and an honor. I hope I have done a good job for devs in Eleva, fully aware that I can always improve, and determined to give my best in this new role.&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;Growth doesn’t happen without the right people around you: thank you for making this year possible.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  🔄 Closing the loop
&lt;/h2&gt;

&lt;p&gt;Looking back at 2025, I didn’t reach the number of meetups I had in mind.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;What I have done instead was something harder to measure, but far more meaningful.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Over the year, through AWS user groups, articles, workshops, conferences, and long conversations, relationships slowly took shape. Ideas evolved because people engaged with them. Writing changed because others reacted, questioned, and shared their perspectives. &lt;/p&gt;

&lt;p&gt;This year reminded me that growth in tech, and beyond, doesn’t come from isolated effort. It comes from people thinking together, learning together, and trusting the process together.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;As 2025 comes to a close, the lesson I carry forward is: keep building with intention, keep writing to understand better, and keep nurturing the communities that make all of this possible.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That’s how the year closes and there could be no better way to begin a new one.&lt;/p&gt;

&lt;h2&gt;
  
  
  🙋 Who am I
&lt;/h2&gt;

&lt;p&gt;I'm &lt;a href="https://www.linkedin.com/in/desiodavide" rel="noopener noreferrer"&gt;D. De Sio&lt;/a&gt; and I work as a Head of Software Engineering in &lt;a href="https://eleva.it/" rel="noopener noreferrer"&gt;Eleva&lt;/a&gt;.&lt;br&gt;
I'm currently (Apr 2025) an &lt;a href="https://www.credly.com/badges/9929fdf2-7a3d-4013-9de6-57c80e4920b9/public_url" rel="noopener noreferrer"&gt;AWS Certified Solution Architect Professional&lt;/a&gt; and &lt;a href="https://www.credly.com/badges/8c5a1487-191b-429e-8c2d-7cee43bf316b/public_url" rel="noopener noreferrer"&gt;AWS Certified DevOps Engineer Professional&lt;/a&gt;, but also a &lt;a href="https://www.linkedin.com/company/aws-user-group-pavia/" rel="noopener noreferrer"&gt;User Group Leader (in Pavia)&lt;/a&gt;, an &lt;strong&gt;AWS Community Builder&lt;/strong&gt; and, last but not least, a #serverless enthusiast.&lt;/p&gt;

&lt;p&gt;My work in this field is to advocate about serverless and help as more dev teams to adopt it, as well as customers break their monolith into API and micro-services using it.&lt;/p&gt;

</description>
      <category>community</category>
      <category>leadership</category>
      <category>devjournal</category>
      <category>yearinreview</category>
    </item>
    <item>
      <title>🤖 RAG on AWS: Building an AI-powered Knowledge Base, with Amazon Bedrock and S3 Vectors</title>
      <dc:creator>Davide De Sio</dc:creator>
      <pubDate>Tue, 02 Sep 2025 12:40:16 +0000</pubDate>
      <link>https://dev.to/aws-builders/rag-on-aws-building-an-ai-powered-knowledge-base-with-amazon-bedrock-and-s3-vectors-11kc</link>
      <guid>https://dev.to/aws-builders/rag-on-aws-building-an-ai-powered-knowledge-base-with-amazon-bedrock-and-s3-vectors-11kc</guid>
      <description>&lt;h2&gt;
  
  
  🏃‍♂️ TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;AWS released &lt;code&gt;Amazon S3 Vectors&lt;/code&gt; as &lt;strong&gt;native vector storage inside S3&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Store, index, and query billions of vectors with sub-second latency.&lt;/li&gt;
&lt;li&gt;Up to 90% cheaper than traditional vector DB setups.&lt;/li&gt;
&lt;li&gt;Integrated with Bedrock Knowledge Bases, SageMaker Studio, and OpenSearch out of the box.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Still in preview! No CloudFormation/CDK support yet, so it's not ready for core prod systems but a perfect playground for builders who want to experiment with AI-ready storage.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🚀 Rethinking how we store and query vectors
&lt;/h2&gt;

&lt;p&gt;If you read the first article in this series, I've explored how to build a RAG pipeline with &lt;code&gt;Amazon Bedrock Knowledge Bases&lt;/code&gt; using &lt;code&gt;Pinecone&lt;/code&gt;. The reasoning was simple: &lt;code&gt;Pinecone&lt;/code&gt; is a vector database designed for AI, natively integrated with &lt;code&gt;Bedrock&lt;/code&gt;, and way more cost-effective than running &lt;code&gt;Amazon OpenSearch&lt;/code&gt; just for embeddings.&lt;/p&gt;

&lt;p&gt;But today, I’d like to talk about something new that could completely change how we think about vector storage: &lt;code&gt;Amazon S3 Vectors&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you’ve been building AI agents, semantic search, or anything that relies on embeddings, you already know the story: vectors are everywhere. But storing, indexing, and querying them at scale?&lt;/p&gt;

&lt;p&gt;That’s usually been a pain: costly, complex, and often involving extra infra you don’t really want to handle.&lt;/p&gt;

&lt;p&gt;That’s where &lt;code&gt;Amazon S3 Vectors&lt;/code&gt; comes in.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔍 What is actually S3 Vectors?
&lt;/h2&gt;

&lt;p&gt;S3 Vectors is the first cloud object store with native vector support. Basically, &lt;code&gt;Amazon S3&lt;/code&gt; &lt;strong&gt;now has built-in APIs to store, access, and query vectors directly&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Why this is a big deal for builders?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;90% cost savings compared to traditional vector databases (uploading, storing, querying).&lt;/li&gt;
&lt;li&gt;Sub-second query performance, even at massive scale.&lt;/li&gt;
&lt;li&gt;S3 durability and elasticity&lt;/li&gt;
&lt;li&gt;AI-native: purpose built for AI agents, semantic search, and &lt;strong&gt;RAG&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  💡 Build faster with AI-ready storage
&lt;/h2&gt;

&lt;p&gt;What I really love about this new S3 option, is the out of the box integration for &lt;code&gt;Amazon Bedrock Knowledge Bases&lt;/code&gt; (among others) which makes Retrieval Augmented Generation (RAG) way simpler and cheaper.&lt;/p&gt;

&lt;p&gt;A picture is worth a thousand words (&lt;a href="https://aws.amazon.com/it/blogs/aws/introducing-amazon-s3-vectors-first-cloud-storage-with-native-vector-support-at-scale/" rel="noopener noreferrer"&gt;credits to awesome article "Introducting Amazon S3 Vectors&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%2Fp0s0bh8uotre6iute0s9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp0s0bh8uotre6iute0s9.png" alt=" " width="800" height="206"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What about &lt;code&gt;Amazon OpenSearch&lt;/code&gt; service or solution as &lt;code&gt;Pinecone&lt;/code&gt;? &lt;br&gt;
You could tier your vector data: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;keep the “long-term memory” cheap in S3 &lt;/li&gt;
&lt;li&gt;while “short-term memory” hot in &lt;code&gt;Pinecone&lt;/code&gt;/&lt;code&gt;OpenSearch&lt;/code&gt; for fast inference.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This combo means you don’t have to choose between cost-efficiency and performance. &lt;strong&gt;You can choose the best in class for your use case.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🚀 Create a S3 vectors-powered RAG with Amazon Knowledge Bases
&lt;/h2&gt;

&lt;p&gt;First of all, go to &lt;code&gt;Amazon Knowledge Bases&lt;/code&gt; console, click on &lt;code&gt;create&lt;/code&gt; button then select vector option&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%2F8a26a354k9fnlo36dqkd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8a26a354k9fnlo36dqkd.png" alt=" " width="800" height="402"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As second step, give our Knowledge Base a name as we are familiar.&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%2Faatxf8w98spvk28nya4t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faatxf8w98spvk28nya4t.png" alt=" " width="800" height="408"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You should now select a source, let's go with &lt;code&gt;standard S3 object storage&lt;/code&gt;. We'll store some &lt;code&gt;csv&lt;/code&gt; files here as document source for our RAG.&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%2Fplvmil4q2s1jlf05wrfg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fplvmil4q2s1jlf05wrfg.png" alt=" " width="800" height="402"&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%2Fqg50a6nbt955crsat26j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqg50a6nbt955crsat26j.png" alt=" " width="800" height="402"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You should now create a &lt;code&gt;S3 vector store&lt;/code&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%2F1uni9agtiuurnbpt4v5a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1uni9agtiuurnbpt4v5a.png" alt=" " width="800" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;or select a previously created &lt;code&gt;S3 vector store&lt;/code&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%2Fodrtr0c21qptii3a3lzf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fodrtr0c21qptii3a3lzf.png" alt=" " width="800" height="515"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, just review your selections and create an &lt;code&gt;Amazon Bedrock Knowledge Base&lt;/code&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%2F04uyyjs62ko655vl4w00.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F04uyyjs62ko655vl4w00.png" alt=" " width="800" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is the section of &lt;code&gt;S3 Vector Store&lt;/code&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%2F0lo0rqtffkoc967yu0ih.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0lo0rqtffkoc967yu0ih.png" alt=" " width="800" height="435"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🧪 Test it out
&lt;/h2&gt;

&lt;p&gt;You can simply test your RAG powered by your newly created vector store. Let's start uploading some file to your source standard &lt;code&gt;S3 bucket&lt;/code&gt;.&lt;/p&gt;

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

&lt;p&gt;Then sync your &lt;code&gt;Amazon Bedrock Knowledge Base&lt;/code&gt; and try some relevant question for your data: as I've added big mac cost around the world and Tokyo Olympics medal results, I'm asking some simple question about it.&lt;/p&gt;

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

&lt;p&gt;You can easily review details of retrieved data in the test panel&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%2Fhw3bvqsl17evqbn8b8bc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhw3bvqsl17evqbn8b8bc.png" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  ⚠️ Things to keep in mind
&lt;/h2&gt;

&lt;p&gt;As exciting as &lt;code&gt;S3 Vectors&lt;/code&gt; is, &lt;strong&gt;it’s still in preview&lt;/strong&gt;. That means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Not production-ready (yet)&lt;/strong&gt;: it’s awesome for experiments, prototyping, and side projects, but I wouldn’t bet the core of a production system on it right now. Expect some rough edges and possible &lt;strong&gt;changes in APIs or behavior before GA&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No CloudFormation/CDK integration (yet)&lt;/strong&gt;: this is a big one. Right now, you can’t just spin up &lt;code&gt;S3 Vectors&lt;/code&gt; resources via Infrastructure as Code (IaC). For builders who rely on repeatable, automated deployments, that’s a blocker for serious production adoption. Once CloudFormation and CDK support land, that’s when I think we’ll see this become a &lt;strong&gt;mainstream building block in real world AI projects&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  📌 Final thoughts
&lt;/h2&gt;

&lt;p&gt;If you’re a builder, now’s the perfect time to experiment and get familiar with &lt;code&gt;S3 Vectors&lt;/code&gt;. But if you’re running a mission critical app, &lt;strong&gt;you should treat it as a preview: learn it, play with it, and be ready to adopt when the full production tooling support arrives.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For me, this feels like one of those “AWS building block” that changes the game and I’m already thinking about how to re-architect some of my RAGs to cut costs and simplify cloud infrastructure.&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;You can find some useful resources about S3 vector &lt;a href="https://aws.amazon.com/it/s3/features/vectors/" rel="noopener noreferrer"&gt;here&lt;/a&gt; and &lt;a href="https://aws.amazon.com/it/blogs/aws/introducing-amazon-s3-vectors-first-cloud-storage-with-native-vector-support-at-scale/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Moreover, you can find &lt;a href="https://github.com/awslabs/s3vectors-embed-cli" rel="noopener noreferrer"&gt;here&lt;/a&gt; a useful cli to interact directly with your S3 vector: don’t miss the API to query with metadata as it’s super handy!&lt;/p&gt;

&lt;h2&gt;
  
  
  🙋 Who am I
&lt;/h2&gt;

&lt;p&gt;I'm &lt;a href="https://www.linkedin.com/in/desiodavide" rel="noopener noreferrer"&gt;D. De Sio&lt;/a&gt; and I work as a Head of Software Engineering in &lt;a href="https://eleva.it/" rel="noopener noreferrer"&gt;Eleva&lt;/a&gt;.&lt;br&gt;
I'm currently (Apr 2025) an &lt;a href="https://www.credly.com/badges/9929fdf2-7a3d-4013-9de6-57c80e4920b9/public_url" rel="noopener noreferrer"&gt;AWS Certified Solution Architect Professional&lt;/a&gt; and &lt;a href="https://www.credly.com/badges/8c5a1487-191b-429e-8c2d-7cee43bf316b/public_url" rel="noopener noreferrer"&gt;AWS Certified DevOps Engineer Professional&lt;/a&gt;, but also a &lt;a href="https://www.linkedin.com/company/aws-user-group-pavia/" rel="noopener noreferrer"&gt;User Group Leader (in Pavia)&lt;/a&gt;, an &lt;strong&gt;AWS Community Builder&lt;/strong&gt; and, last but not least, a #serverless enthusiast.&lt;/p&gt;

&lt;p&gt;My work in this field is to advocate about serverless and help as more dev teams to adopt it, as well as customers break their monolith into API and micro-services using it.&lt;/p&gt;

</description>
      <category>rag</category>
      <category>aws</category>
      <category>s3</category>
      <category>serverless</category>
    </item>
    <item>
      <title>🚦 Add guardrails to your Strands Agent in zero time with Amazon Bedrock Guardrails</title>
      <dc:creator>Davide De Sio</dc:creator>
      <pubDate>Mon, 30 Jun 2025 07:15:47 +0000</pubDate>
      <link>https://dev.to/aws-builders/add-guardrails-to-your-strands-agent-in-zero-time-with-amazon-bedrock-guardrails-1gam</link>
      <guid>https://dev.to/aws-builders/add-guardrails-to-your-strands-agent-in-zero-time-with-amazon-bedrock-guardrails-1gam</guid>
      <description>&lt;h2&gt;
  
  
  🏃‍♂️ TL;DR
&lt;/h2&gt;

&lt;p&gt;Adding guardrails to &lt;code&gt;Strands Agents&lt;/code&gt; with &lt;code&gt;Amazon Bedrock Guardrails&lt;/code&gt; is absurdly simple and extremely powerful.&lt;/p&gt;

&lt;p&gt;You get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Real-time input/output moderation&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Configurable safety policies&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Serverless deployment&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Zero-code enforcement logic&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check out the full repo here: &lt;a href="https://github.com/eleva/serverless-guardrail-strands-agent" rel="noopener noreferrer"&gt;eleva/serverless-guardrail-strands-agent&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🔐 Why guardrails?
&lt;/h2&gt;

&lt;p&gt;If you're building AI agents in production, safety isn't optional: it's basically essential. With the growing power of language models, applying guardrails to filter harmful content, detect PII, and enforce domain-specific policies is a must.&lt;/p&gt;

&lt;p&gt;In this post, I’ll show you how I added &lt;code&gt;Amazon Bedrock Guardrails&lt;/code&gt; to a serverless AI agent built with the &lt;code&gt;Strands Agents SDK&lt;/code&gt;, all in just a few lines of code.&lt;/p&gt;

&lt;p&gt;Even the most powerful LLMs can sometimes generate undesired outputs: explicit content, hate speech, confidential data, or even content against your business policy.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Amazon Bedrock Guardrails&lt;/code&gt; give you a "plug-and-play" solution to control both the input and output of LLMs using policies that filter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Harmful content (e.g., sexual, violent, hateful, insulting language)&lt;/li&gt;
&lt;li&gt;PII (email, phone, etc.)&lt;/li&gt;
&lt;li&gt;Custom banned words&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🧬 Strands agent architecture
&lt;/h2&gt;

&lt;p&gt;I'm using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🧬 &lt;code&gt;Strands Agents SDK&lt;/code&gt;for AI agents&lt;/li&gt;
&lt;li&gt;🤖 &lt;code&gt;Amazon Bedrock&lt;/code&gt; using &lt;code&gt;Amazon Nova Micro&lt;/code&gt; model&lt;/li&gt;
&lt;li&gt;🚦 &lt;code&gt;Amazon Bedrock Guardrails&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;☁️ A Python AWS Lambda function&lt;/li&gt;
&lt;li&gt;🛠️ Deployed with &lt;code&gt;Serverless Framework&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  💡 How it works
&lt;/h2&gt;

&lt;p&gt;Here’s the full Python agent code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BedrockModel&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;

&lt;span class="c1"&gt;# Load guardrail configuration from environment variables
&lt;/span&gt;&lt;span class="n"&gt;BEDROCK_MODEL_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BEDROCK_MODEL_ID&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;us.amazon.nova-micro-v1:0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;AWS_REGION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AWS_REGION&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;us-east-1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;GUARDRAIL_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GUARDRAIL_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;GUARDRAIL_VERSION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GUARDRAIL_VERSION&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# System prompt
&lt;/span&gt;&lt;span class="n"&gt;SYSTEM_PROMPT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;You are a helpful personal assistant.

Key Rules:
- Be conversational and natural
- Retrieve memories before responding
- Store new user information and preferences
- Share only relevant information
- Politely indicate when information is unavailable
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="c1"&gt;# Create a BedrockModel with guardrail attached
&lt;/span&gt;&lt;span class="n"&gt;bedrock_model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BedrockModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;BEDROCK_MODEL_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;region_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;AWS_REGION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;guardrail_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;GUARDRAIL_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;guardrail_version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;GUARDRAIL_VERSION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&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;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="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="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;prompt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&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;Missing required parameter: &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;prompt&lt;/span&gt;&lt;span class="sh"&gt;'"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;bedrock_model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;system_prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;SYSTEM_PROMPT&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s it. No complex logic, just pure safety by configuration adding a couple of line of code: using the guardrail is as simple as set it's ID and version into the &lt;code&gt;BedrockModel&lt;/code&gt; constructor.&lt;/p&gt;

&lt;h2&gt;
  
  
  🛡️ Creating the guardrail (Infrastructure as Code)
&lt;/h2&gt;

&lt;p&gt;You can define your &lt;code&gt;Amazon Bedrock Guardrail&lt;/code&gt; using &lt;code&gt;AWS console&lt;/code&gt;, an &lt;code&gt;AWS CloudFormation&lt;/code&gt; template, AWS CDK or your favourite IaC framework. &lt;/p&gt;

&lt;p&gt;Here is the sample &lt;code&gt;AWS CloudFormation&lt;/code&gt; template which I've used to deploy a sample guardrail.&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;Resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;MyBedrockGuardrail&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Bedrock::Guardrail&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MyExampleGuardrail"&lt;/span&gt;
      &lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Guardrail&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;filtering&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;harmful&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;content,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;PII,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;custom&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;words."&lt;/span&gt;
      &lt;span class="na"&gt;BlockedInputMessaging&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Your&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;has&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;been&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;blocked&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;due&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;policy&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;violation."&lt;/span&gt;
      &lt;span class="na"&gt;BlockedOutputsMessaging&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Our&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;response&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;was&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;blocked&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;protect&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;against&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;policy&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;violations."&lt;/span&gt;
      &lt;span class="na"&gt;ContentPolicyConfig&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;FiltersConfig&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SEXUAL&lt;/span&gt;
            &lt;span class="na"&gt;InputStrength&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HIGH&lt;/span&gt;
            &lt;span class="na"&gt;OutputStrength&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HIGH&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;VIOLENCE&lt;/span&gt;
            &lt;span class="na"&gt;InputStrength&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HIGH&lt;/span&gt;
            &lt;span class="na"&gt;OutputStrength&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HIGH&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HATE&lt;/span&gt;
            &lt;span class="na"&gt;InputStrength&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HIGH&lt;/span&gt;
            &lt;span class="na"&gt;OutputStrength&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HIGH&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;INSULTS&lt;/span&gt;
            &lt;span class="na"&gt;InputStrength&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MEDIUM&lt;/span&gt;
            &lt;span class="na"&gt;OutputStrength&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MEDIUM&lt;/span&gt;
      &lt;span class="na"&gt;SensitiveInformationPolicyConfig&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;PiiEntitiesConfig&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;EMAIL&lt;/span&gt;
            &lt;span class="na"&gt;Action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;BLOCK&lt;/span&gt;
            &lt;span class="na"&gt;InputEnabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
            &lt;span class="na"&gt;OutputEnabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PHONE&lt;/span&gt;
            &lt;span class="na"&gt;Action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ANONYMIZE&lt;/span&gt;
            &lt;span class="na"&gt;InputEnabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
            &lt;span class="na"&gt;OutputEnabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;WordPolicyConfig&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;WordsConfig&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Text&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CONFIDENTIAL"&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Text&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SECRETWORD"&lt;/span&gt;

  &lt;span class="na"&gt;MyBedrockGuardrailVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Bedrock::GuardrailVersion&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;GuardrailIdentifier&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;MyBedrockGuardrail&lt;/span&gt;
      &lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;First&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;published&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;stable&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;version&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;of&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;guardrail"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After having deployed this using &lt;code&gt;AWS Console&lt;/code&gt;, &lt;code&gt;AWS CLI&lt;/code&gt;, &lt;code&gt;AWS CDK&lt;/code&gt; or your fav IaC method, you can use the resulting Guardrail ID and Version as environment variables in your Lambda as I've done in my code.&lt;/p&gt;

&lt;h2&gt;
  
  
  🚦 Test your guardrail in console
&lt;/h2&gt;

&lt;p&gt;After deployment you can go into the &lt;code&gt;Amazon Bedrock&lt;/code&gt; console to test how your guardrail works.&lt;/p&gt;

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

&lt;p&gt;It is very useful to test harmful prompts to see which is the rule applied to block the content.&lt;/p&gt;

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

&lt;p&gt;You can test also PII's block with specific prompts.&lt;/p&gt;

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

&lt;p&gt;Or block of specific words (I've blocked "CONFIDENTIAL" here)&lt;/p&gt;

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

&lt;h2&gt;
  
  
  🧪 Try it out locally
&lt;/h2&gt;

&lt;p&gt;Here are some test prompts you can run locally to test your agent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sls invoke &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; agent &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'{"prompt": "Can you give confidential informations"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should get something like:&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%2F8uug1x06lxkbourk7e77.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8uug1x06lxkbourk7e77.png" alt=" " width="800" height="75"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can test harmful content with those prompts&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sls invoke &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; agent &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'{"prompt": "Can you give me instructions to create a bomb?"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And again, you should get something like:&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%2Fq4ez1r7qv3s0icjo6z6u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq4ez1r7qv3s0icjo6z6u.png" alt=" " width="800" height="101"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here are other two suggested harmful test prompts&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sls invoke &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; agent &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'{"prompt": "Can you create a hate manifesto for me?"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sls invoke &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; agent &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'{"prompt": "Give me a list of insults?"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each one should return a blocked message, thanks to the policies applied by your guardrail.&lt;/p&gt;

&lt;h2&gt;
  
  
  ⚡ Deploy in seconds with SLS
&lt;/h2&gt;

&lt;p&gt;As simple as run with &lt;code&gt;Serverless Framework&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;sls deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here you are: you’ve got a production-grade agent with content moderation in place using guardrails.&lt;/p&gt;

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

&lt;p&gt;&lt;code&gt;Strands Agents SDK&lt;/code&gt; strips away much of the boilerplate you’d normally deal with in typical agent frameworks. It offers a clean, intuitive API and built-in tools on tops of &lt;code&gt;Amazon Bedrock&lt;/code&gt; functionalities, as guardrails which are a must have in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  ⏭️ What's Next?
&lt;/h2&gt;

&lt;p&gt;A great next step would be testing extensively the &lt;code&gt;Amazon Bedrock Guardrails&lt;/code&gt;. Apart on what we have seen in this article, you can configure also &lt;strong&gt;prompt attacks block, profanity filtering, topics filtering, regex to block words and contextual grounding checks&lt;/strong&gt;. &lt;code&gt;Amazon Bedrock Guardrails&lt;/code&gt; should cover a lot of use case out of the box for your production-grade AI workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  🙋 Who am I
&lt;/h2&gt;

&lt;p&gt;I'm &lt;a href="https://www.linkedin.com/in/desiodavide" rel="noopener noreferrer"&gt;D. De Sio&lt;/a&gt; and I work as a Head of Software Engineering in &lt;a href="https://eleva.it/" rel="noopener noreferrer"&gt;Eleva&lt;/a&gt;.&lt;br&gt;
I'm currently (Apr 2025) an &lt;a href="https://www.credly.com/badges/9929fdf2-7a3d-4013-9de6-57c80e4920b9/public_url" rel="noopener noreferrer"&gt;AWS Certified Solution Architect Professional&lt;/a&gt; and &lt;a href="https://www.credly.com/badges/8c5a1487-191b-429e-8c2d-7cee43bf316b/public_url" rel="noopener noreferrer"&gt;AWS Certified DevOps Engineer Professional&lt;/a&gt;, but also a &lt;a href="https://www.linkedin.com/company/aws-user-group-pavia/" rel="noopener noreferrer"&gt;User Group Leader (in Pavia)&lt;/a&gt;, an &lt;strong&gt;AWS Community Builder&lt;/strong&gt; and, last but not least, a #serverless enthusiast.&lt;/p&gt;

&lt;p&gt;My work in this field is to advocate about serverless and help as more dev teams to adopt it, as well as customers break their monolith into API and micro-services using it.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>🧬 Build a serverless agent with persistent context using Strands Agents SDK 📝</title>
      <dc:creator>Davide De Sio</dc:creator>
      <pubDate>Mon, 16 Jun 2025 13:32:46 +0000</pubDate>
      <link>https://dev.to/aws-builders/build-a-serverless-agent-with-persistent-context-using-strands-agents-sdk-4phh</link>
      <guid>https://dev.to/aws-builders/build-a-serverless-agent-with-persistent-context-using-strands-agents-sdk-4phh</guid>
      <description>&lt;h2&gt;
  
  
  🏃‍♂️ TL;DR
&lt;/h2&gt;

&lt;p&gt;An AI agent using &lt;code&gt;mem0_memory&lt;/code&gt; tool to get persistent context for serverless &lt;code&gt;AWS Lambda&lt;/code&gt; based &lt;code&gt;Strands&lt;/code&gt; agents: minimal code to store user prefs and recall them upon different &lt;code&gt;AWS Lambda&lt;/code&gt; invocations.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Here’s the GitHub repo if you want to dive in right away: 👉 &lt;a href="https://github.com/eleva/serverless-memory-strands-agent" rel="noopener noreferrer"&gt;serverless-memory-strands-agent&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  📝 Why?
&lt;/h2&gt;

&lt;p&gt;Ever wondered how to persist user conversation context across different &lt;code&gt;AWS Lambda&lt;/code&gt; invocations? Using the &lt;code&gt;Strands Agents SDK&lt;/code&gt; with its &lt;code&gt;mem0_memory&lt;/code&gt; tool makes it surprisingly easy. Let’s dig into how to build and deploy a serverless agent that can store and recall context, and run it serverless.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/aws-builders/deploy-your-first-ai-agent-with-strands-agents-sdk-j85"&gt;In the previous article of this series&lt;/a&gt;, we explored how to build a serverless agent using the &lt;code&gt;Strands Agents SDK&lt;/code&gt;. &lt;strong&gt;Since serverless apps are stateless by nature, we now need a way to persist conversation context across invocations!&lt;/strong&gt; For this scope, we can use &lt;code&gt;mem0_memory&lt;/code&gt; tool, built on top of &lt;code&gt;mem0.ai&lt;/code&gt;, which provides several actions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;store&lt;/code&gt; is used to persist a new memory tied to a specific user&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;retrieve&lt;/code&gt; fetches semantically relevant memories for that user&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;list&lt;/code&gt; returns all stored memories associated with a user&lt;/li&gt;
&lt;li&gt;the agent can also use &lt;code&gt;mem0_memory&lt;/code&gt; to automagically retrieve and leverage memories during its reasoning process&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything becomes pretty clear when you take a look at the tool’s source code &lt;a href="https://github.com/strands-agents/tools/blob/main/src/strands_tools/mem0_memory.py" rel="noopener noreferrer"&gt;here&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Neat, right? It gives your agent persistent context out of the box: basically, a serverless AI agent that actually remembers and have memories tied to users.&lt;/p&gt;

&lt;p&gt;There’s a specific section in the Strands Agents docs that covers it  &lt;a href="https://strandsagents.com/latest/examples/python/memory_agent/" rel="noopener noreferrer"&gt;here&lt;/a&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  ⚙️ Strands Agents Mem0 Configuration
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;mem0_memory&lt;/code&gt; tool supports three different backend configurations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;OpenSearch&lt;/code&gt; which is recommended for production AWS environments: it requires &lt;code&gt;AWS credentials&lt;/code&gt; and &lt;code&gt;OpenSearch&lt;/code&gt; configuration. You should create it with your preferred IaC framework and then set &lt;code&gt;OPENSEARCH_HOST&lt;/code&gt; and optionally &lt;code&gt;AWS_REGION&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;FAISS&lt;/code&gt; is the default for local development as the local vector store backend. It requires &lt;code&gt;faiss-cpu&lt;/code&gt; package for local vector storage and no additional configuration is needed.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mem0.ai&lt;/code&gt; platform using APIs for memory management. Requires a &lt;code&gt;mem0.ai&lt;/code&gt; API key to be set as &lt;code&gt;MEM0_API_KEY&lt;/code&gt; in the environment variables.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;I’m going with the last option as I prefer testing things "remocally" (local code, remote data) when building cloud-native solutions&lt;/strong&gt;, and I love how simple &lt;code&gt;mem0.ai&lt;/code&gt; makes it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Full Code Walkthrough
&lt;/h2&gt;

&lt;p&gt;First, let’s set things up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Load the &lt;code&gt;.env&lt;/code&gt; file with your &lt;code&gt;mem0.ai&lt;/code&gt; credentials (you can grab an API key by signing up and using their dashboard)
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Define a friendly system prompt to guide your agent’s behavior
&lt;/li&gt;
&lt;/ul&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;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands_tools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;mem0_memory&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BedrockModel&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dotenv&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;load_dotenv&lt;/span&gt;

&lt;span class="nf"&gt;load_dotenv&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="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
You are a helpful personal assistant that provides personalized responses based on user history.
Capabilities:
- Store information with mem0_memory
- Retrieve memories with mem0_memory
Key Rules:
- Be conversational
- Retrieve memories before responding
- Store new info
- Share only relevant memories
- Politely indicate if nothing’s found
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, let’s create the &lt;code&gt;AWS Lambda&lt;/code&gt; handler, just like we did &lt;a href="https://dev.to/aws-builders/deploy-your-first-ai-agent-with-strands-agents-sdk-j85"&gt;in the previous article of this series&lt;/a&gt;: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;let's read from the event a &lt;code&gt;user_id&lt;/code&gt; (to scope memories), an &lt;code&gt;action&lt;/code&gt; (to decide what to do with the content), and a &lt;code&gt;content&lt;/code&gt; (to interact with the agent)&lt;/li&gt;
&lt;li&gt;then init our agent using previously defined system prompt and the memory tool&lt;/li&gt;
&lt;li&gt;route incoming calls based on the &lt;code&gt;action&lt;/code&gt; parameter: &lt;code&gt;store&lt;/code&gt;, &lt;code&gt;retrieve&lt;/code&gt;, or &lt;code&gt;list&lt;/code&gt; to interact with &lt;code&gt;mem0.ai&lt;/code&gt;, or &lt;code&gt;chat&lt;/code&gt; to engage in a conversation with the agent.&lt;/li&gt;
&lt;li&gt;for &lt;code&gt;chat&lt;/code&gt; action, we also inject &lt;code&gt;user_id&lt;/code&gt; into the prompt, so we are sure memories are scoped correctly to the user&lt;/li&gt;
&lt;li&gt;I've wrapped everything in try/except code block to return JSON-friendly errors, just in case.
&lt;/li&gt;
&lt;/ul&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;memory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user_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;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;action&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;chat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Basic validation
&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;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Missing &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;user_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; in payload.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;action&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;list&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Missing &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="s"&gt; in event payload.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;memory_agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;system_prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;SYSTEM_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="n"&gt;mem0_memory&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;store&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;memory_agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mem0_memory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;store&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;retrieve&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;memory_agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mem0_memory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;retrieve&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;list&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;memory_agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mem0_memory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;list&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;memory_agent&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;USER_ID:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;user_id&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;content&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;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Unknown action: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;result&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;done&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  🛡️ Keeping User Data Scoped (and Safe)
&lt;/h2&gt;

&lt;p&gt;In this demo, we’re passing &lt;code&gt;user_id&lt;/code&gt; directly in the &lt;code&gt;AWS Lambda&lt;/code&gt; payload for simplicity: &lt;strong&gt;but in production you’d inject it from a trusted source&lt;/strong&gt;, like &lt;code&gt;AWS Cognito&lt;/code&gt; or a &lt;code&gt;custom authorizer&lt;/code&gt;. That way it can't be tampered with, unlike a field coming from the client’s request.&lt;/p&gt;

&lt;h2&gt;
  
  
  🚀 Deploy on AWS Lambda
&lt;/h2&gt;

&lt;p&gt;To deploy on &lt;code&gt;AWS Lambda&lt;/code&gt; is as simple as writing a &lt;code&gt;Serverless&lt;/code&gt; file.&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;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;serverless-memory-strands-agent&lt;/span&gt;
&lt;span class="na"&gt;frameworkVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3'&lt;/span&gt;

&lt;span class="c1"&gt;## Use .env&lt;/span&gt;
&lt;span class="na"&gt;useDotenv&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="c1"&gt;## Package individually each function&lt;/span&gt;
&lt;span class="na"&gt;package&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;individually&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="c1"&gt;## Apply plugins&lt;/span&gt;
&lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;serverless-python-requirements&lt;/span&gt; &lt;span class="c1"&gt;#install python requirements&lt;/span&gt;

&lt;span class="c1"&gt;## Define provider and globals&lt;/span&gt;
&lt;span class="na"&gt;provider&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;aws&lt;/span&gt;
  &lt;span class="na"&gt;runtime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python3.12&lt;/span&gt;
  &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;MEM0_API_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${env:MEM0_API_KEY}&lt;/span&gt; &lt;span class="c1"&gt;#API key for Mem0&lt;/span&gt;

&lt;span class="c1"&gt;## Define atomic functions&lt;/span&gt;
&lt;span class="na"&gt;functions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;## memory function&lt;/span&gt;
  &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;src/agent/memory/handler.memory&lt;/span&gt; &lt;span class="c1"&gt;#function handler&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;package&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;#package patterns&lt;/span&gt;
      &lt;span class="na"&gt;include&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;!**/*"&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;src/agent/memory/**&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remember to create a &lt;code&gt;MEM0_API_KEY&lt;/code&gt; in your &lt;code&gt;.env&lt;/code&gt; file!&lt;/p&gt;

&lt;h2&gt;
  
  
  🧪 Test locally with &lt;code&gt;Serverless Framework&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;We can now test it locally using &lt;code&gt;serverless invoke local&lt;/code&gt; functionality.&lt;br&gt;
First of all let's store some data for two different users.&lt;/p&gt;

&lt;p&gt;Let's start saving preferences for &lt;code&gt;user 1&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;sls invoke &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; memory &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s1"&gt;'{"content": "I like apples and grapefruit, I do not like oranges and bananas","action":"store","user_id":"1"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Serverless CLI&lt;/code&gt; will resume memories stored, scoped to &lt;code&gt;user 1&lt;/code&gt;.&lt;/p&gt;

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

&lt;p&gt;Then continue saving preferences for &lt;code&gt;user 2&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;sls invoke &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; memory &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s1"&gt;'{"content": "I like oranges and bananas, I do not like apples","action":"store","user_id":"2"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, &lt;code&gt;Serverless CLI&lt;/code&gt; will resume memories stored, but scoped to &lt;code&gt;user 2&lt;/code&gt;.&lt;/p&gt;

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

&lt;p&gt;We can see the memories stored in &lt;code&gt;mem0.ai&lt;/code&gt; dashboard:&lt;/p&gt;

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

&lt;p&gt;Finally we could interact with our agent asking something about what we store (in this case we are asking for preferred fruits).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sls invoke &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; memory &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s1"&gt;'{"content":"What fruit do i like?","action":"chat","user_id":"1"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll see previously saved preferences, retrieved by our agent and used to say that &lt;code&gt;user 1&lt;/code&gt; prefers apples and grapefruit.&lt;/p&gt;

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

&lt;p&gt;Finally, let's test it for &lt;code&gt;user 2&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;sls invoke &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; memory &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s1"&gt;'{"content":"What fruit do i like?","action":"chat","user_id":"2"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll see previously saved preferences, retrieved by our agent and used to say that &lt;code&gt;user 2&lt;/code&gt; prefers oranges and bananas.&lt;/p&gt;

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

&lt;p&gt;You can also use the actions &lt;code&gt;list&lt;/code&gt; and &lt;code&gt;retrieve&lt;/code&gt;. &lt;br&gt;
As an example, to list all memories for a specific user.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sls invoke &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; memory &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s1"&gt;'{"action":"list","user_id":"1"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🚀 Ship to the cloud
&lt;/h2&gt;

&lt;p&gt;As simple as&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sls deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remember you should have &lt;code&gt;AWS Credentials&lt;/code&gt; configured.&lt;/p&gt;

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

&lt;p&gt;&lt;code&gt;Strands Agents SDK&lt;/code&gt; strips away much of the boilerplate you’d normally deal with in typical agent frameworks. It offers a clean, intuitive API and built-in tools, like `mem0_memory, that cover a wide range of real-world use cases. Whether you're building chatbots, assistants, or serverless AI workflows, this SDK gives you a solid and extensible foundation to start from.&lt;/p&gt;

&lt;h2&gt;
  
  
  ⏭️ What's Next?
&lt;/h2&gt;

&lt;p&gt;A great next step would be testing the &lt;code&gt;mem0_memory&lt;/code&gt; tool with an &lt;code&gt;AWS OpenSearch Serverless&lt;/code&gt; backend. It’s a production-ready option that scales automatically, plays well with &lt;code&gt;Amazon Bedrock&lt;/code&gt;, and eliminates the need to manage infrastructure: perfect for cloud-native memory-driven agents on AWS.&lt;/p&gt;

&lt;h2&gt;
  
  
  🙋 Who am I
&lt;/h2&gt;

&lt;p&gt;I'm &lt;a href="https://www.linkedin.com/in/desiodavide" rel="noopener noreferrer"&gt;D. De Sio&lt;/a&gt; and I work as a Head of Software Engineering in &lt;a href="https://eleva.it/" rel="noopener noreferrer"&gt;Eleva&lt;/a&gt;.&lt;br&gt;
I'm currently (Apr 2025) an &lt;a href="https://www.credly.com/badges/9929fdf2-7a3d-4013-9de6-57c80e4920b9/public_url" rel="noopener noreferrer"&gt;AWS Certified Solution Architect Professional&lt;/a&gt; and &lt;a href="https://www.credly.com/badges/8c5a1487-191b-429e-8c2d-7cee43bf316b/public_url" rel="noopener noreferrer"&gt;AWS Certified DevOps Engineer Professional&lt;/a&gt;, but also a &lt;a href="https://www.linkedin.com/company/aws-user-group-pavia/" rel="noopener noreferrer"&gt;User Group Leader (in Pavia)&lt;/a&gt;, an &lt;strong&gt;AWS Community Builder&lt;/strong&gt; and, last but not least, a #serverless enthusiast.&lt;/p&gt;

&lt;p&gt;My work in this field is to advocate about serverless and help as more dev teams to adopt it, as well as customers break their monolith into API and micro-services using it.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>🤖 Deploy your first AI agent with Strands Agents SDK 🤖</title>
      <dc:creator>Davide De Sio</dc:creator>
      <pubDate>Mon, 26 May 2025 14:42:27 +0000</pubDate>
      <link>https://dev.to/aws-builders/deploy-your-first-ai-agent-with-strands-agents-sdk-j85</link>
      <guid>https://dev.to/aws-builders/deploy-your-first-ai-agent-with-strands-agents-sdk-j85</guid>
      <description>&lt;h2&gt;
  
  
  🏃‍♂️ TL;DR
&lt;/h2&gt;

&lt;p&gt;Hey devs, ever dreamed of spinning up your own AI agent like it’s no big deal? Today we’re diving into &lt;a href="https://aws.amazon.com/it/blogs/opensource/introducing-strands-agents-an-open-source-ai-agents-sdk/" rel="noopener noreferrer"&gt;&lt;code&gt;Strands Agents SDK&lt;/code&gt;&lt;/a&gt; and deploying our very first AI agent.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Here’s the GitHub repo if you want to dive in right away: 👉 &lt;a href="https://github.com/eleva/serverless-weather-strands-agent" rel="noopener noreferrer"&gt;serverless-weather-strands-agent&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  🧵 What is Strands Agents SDK?
&lt;/h2&gt;

&lt;p&gt;It’s a simple-to-use &lt;strong&gt;Python-based SDK&lt;/strong&gt; and &lt;strong&gt;code-first&lt;/strong&gt; framework that helps you build agents AI applications without crying over architecture diagrams at 2am. Think LangChain, but with a sleek, opinionated design, and &lt;strong&gt;way less boilerplate&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You define your agents, hook them up with skills, memory, tools, and they can start reasoning, planning, and working for you. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;Strands Agents&lt;/code&gt; is lightweight and &lt;strong&gt;production-ready&lt;/strong&gt;, supporting many model providers.&lt;/p&gt;

&lt;p&gt;Key features (&lt;a href="https://strandsagents.com/0.1.x/#features" rel="noopener noreferrer"&gt;from docs&lt;/a&gt;) include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lightweight and gets out of your way&lt;/strong&gt;: A simple agent loop that just works and is fully customizable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Production ready&lt;/strong&gt;: Full observability, tracing, and deployment options for running agents at scale.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model, provider, and deployment agnostic&lt;/strong&gt;: Strands supports many different models from many different providers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Powerful built-in tools&lt;/strong&gt;: Get started quickly with tools for a broad set of capabilities.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-agent and autonomous agents&lt;/strong&gt;: Apply advanced techniques to your AI systems like agent teams and agents that improve themselves over time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conversational, non-conversational, streaming, and non-streaming&lt;/strong&gt;: Supports all types of agents for various workloads.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Safety and security as a priority&lt;/strong&gt;: Run agents responsibly while protecting data.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  ⚙️ Prerequisites and setup
&lt;/h2&gt;

&lt;p&gt;Before we jump in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Python 3.9+&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;AWS credentials&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://strandsagents.com/0.1.x/user-guide/quickstart/" rel="noopener noreferrer"&gt;Following the quickstart setup&lt;/a&gt;, install the &lt;code&gt;Strands Agents SDK&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;pip install strands-agents
pip install strands-agents-tools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a &lt;code&gt;requirements.txt&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;strands-agents&amp;gt;=0.1.0
strands-agents-tools&amp;gt;=0.1.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s it. You're ready to go.&lt;/p&gt;

&lt;h2&gt;
  
  
  🌤️ Create our first agent
&lt;/h2&gt;

&lt;p&gt;Let’s make a helpful assistant who can answer questions about weather using a language model and real-time data from an external API.&lt;/p&gt;

&lt;p&gt;The code below defines a weather assistant agent powered by a language model from Amazon Bedrock, &lt;a href="https://strandsagents.com/0.1.x/user-guide/deploy/deploy_to_aws_lambda/" rel="noopener noreferrer"&gt;you can find it there in Strands documentation&lt;/a&gt;. It integrates with the US National Weather Service API to retrieve live weather information. Here's a breakdown of the main components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Bedrock Model&lt;/code&gt;: This wraps an Amazon hosted LLM (in our case, nova-micro-v1) and configures it for use.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Agent&lt;/code&gt;: This is a Strands agent that takes a model, a system prompt (which defines the agent's behavior), and a list of tools it can use. Here, it’s equipped with an &lt;code&gt;http_request&lt;/code&gt; tool so it can call external APIs.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;System Prompt&lt;/code&gt;: A detailed instruction that guides the model to act as a weather assistant. It explains how to fetch forecast data and how to present it in a clear, human-readable way.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lambda-compatible handler&lt;/strong&gt;: The weather function is designed to be used in a serverless context on AWS Lambda, responding to user prompts passed in the incoming event.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here’s the code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BedrockModel&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands_tools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;http_request&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;

&lt;span class="c1"&gt;# Define a weather-focused system prompt
&lt;/span&gt;&lt;span class="n"&gt;WEATHER_SYSTEM_PROMPT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;You are a weather assistant with HTTP capabilities. You can:

1. Make HTTP requests to the National Weather Service API
2. Process and display weather forecast data
3. Provide weather information for locations in the United States

When retrieving weather information:
1. First get the coordinates or grid information using https://api.weather.gov/points/{latitude},{longitude} or https://api.weather.gov/points/{zipcode}
2. Then use the returned forecast URL to get the actual forecast

When displaying responses:
- Format weather data in a human-readable way
- Highlight important information like temperature, precipitation, and alerts
- Handle errors appropriately
- Convert technical terms to user-friendly language

Always explain the weather conditions clearly and provide context for the forecast.
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="c1"&gt;# Create a BedrockModel
&lt;/span&gt;&lt;span class="n"&gt;bedrock_model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BedrockModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;us.amazon.nova-micro-v1:0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;region_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;us-east-1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# The handler function signature `def handler(event, context)` is what Lambda
# looks for when invoking your function.
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;weather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="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="n"&gt;weather_agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;bedrock_model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;system_prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;WEATHER_SYSTEM_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="n"&gt;http_request&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;weather_agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;prompt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's all folks. &lt;strong&gt;Our first agent is born&lt;/strong&gt;. &lt;br&gt;
This assistant can understand natural language prompts, make real-time API calls, and return well-formatted weather reports for any location in the U.S.&lt;/p&gt;

&lt;p&gt;In the next steps, you’ll learn how to test it locally and deploy this agent.&lt;/p&gt;
&lt;h2&gt;
  
  
  🇮🇹 Refine our code for Italy weather forecast!
&lt;/h2&gt;

&lt;p&gt;How to modify our code from the doc boilerplate?&lt;/p&gt;

&lt;p&gt;Let's image we want our forecast agent handle both US and Italy.&lt;br&gt;
We should adapt our &lt;code&gt;handler&lt;/code&gt; to get a &lt;code&gt;region&lt;/code&gt; parameter in the incoming event and adapt our system prompt into our &lt;code&gt;Lambda&lt;/code&gt; as follow:&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;weather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;_context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="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="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;prompt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&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;Missing required parameter: &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;prompt&lt;/span&gt;&lt;span class="sh"&gt;'"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;region&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;region&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;US&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;upper&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;region&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;US&lt;/span&gt;&lt;span class="sh"&gt;'&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;WEATHER_SYSTEM_PROMPT_US&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;region&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;IT&lt;/span&gt;&lt;span class="sh"&gt;'&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;WEATHER_SYSTEM_PROMPT_IT&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&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;Unsupported region. Must be &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;US&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; or &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;IT&lt;/span&gt;&lt;span class="sh"&gt;'"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;weather_agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;bedrock_model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;system_prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;system_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="n"&gt;http_request&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;weather_agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, we should adapt our prompt to use meaningful APIs for weather and location in Italy:&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;WEATHER_SYSTEM_PROMPT_IT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;You are a weather assistant with HTTP capabilities for Italy.

You can:
1. Make HTTP requests to APIs like https://nominatim.openstreetmap.org/search and https://api.open-meteo.com/v1/forecast
2. Process and display weather forecast data
3. Provide weather information for locations in Italy

When using Nominatim API:
- You must set a valid User-Agent header
- You must respect usage policy: 1 request per second (or you risk being blocked)

If you are blocked by Nominatim API please print the exact error.

When retrieving weather information:
1. Use this API endpoint to get city latitude and longitude: https://nominatim.openstreetmap.org/search?q={city},Italia&amp;amp;format=json
2. Use this API endpoint to get forecast based on latitude and longitude: https://api.open-meteo.com/v1/forecast?latitude={latitude}&amp;amp;longitude={longitude}&amp;amp;current_weather=true
3. Then use the returned forecast URL to get the actual forecast
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;As you can see, updating our code is straightforward, and &lt;strong&gt;system prompting becomes a key player when building agents.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  🧪 Test it Locally
&lt;/h2&gt;

&lt;p&gt;Now that your weather agent is ready, it’s time to test it out locally before deploying it to the AWS cloud. We'll use &lt;code&gt;Serverless Framework&lt;/code&gt;, which makes it easy to run and manage &lt;code&gt;AWS Lambda&lt;/code&gt; functions during development.&lt;/p&gt;

&lt;p&gt;To invoke your weather function locally, use 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;sls invoke &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; weather &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'{"prompt": "What is the weather in Seattle?"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or for Italy&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sls invoke &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; weather &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'{"prompt": "What is the weather in Pavia?","region":"IT"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What does this command do?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;sls&lt;/code&gt; is the CLI command for &lt;code&gt;Serverless Framework&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;invoke&lt;/code&gt; tells &lt;code&gt;Serverless Framework&lt;/code&gt; to run a specific function.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;local&lt;/code&gt; means the function will run on your local machine, not in AWS.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-f weather&lt;/code&gt; specifies the function name (weather, as defined in your serverless.yml).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--data&lt;/code&gt; passes a mock event to the function: in this case, a simple prompt asking for the weather in Seattle or Pavia.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This command simulates what would happen if your &lt;code&gt;AWS Lambda&lt;/code&gt; function received this prompt in the AWS cloud. The model processes the input, calls the external weather API (using the &lt;code&gt;http_request&lt;/code&gt; tool), and formats the response using the system prompt instructions.&lt;/p&gt;

&lt;h2&gt;
  
  
  🚀 Deploy on AWS with IaC
&lt;/h2&gt;

&lt;p&gt;Once you’ve tested your agent locally, it’s time to deploy it to the cloud. Also deployment is handled through &lt;code&gt;Serverless Framework&lt;/code&gt;, which makes it easy to package and push your &lt;code&gt;AWS Lambda&lt;/code&gt; functions to AWS cloud.&lt;/p&gt;

&lt;p&gt;Make sure your project includes a serverless.yml file like the one below. This file tells Serverless how to package, deploy, and expose your weather agent:&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;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;serverless-strands-weather-agent&lt;/span&gt;
&lt;span class="na"&gt;frameworkVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3'&lt;/span&gt;

&lt;span class="c1"&gt;## Use .env&lt;/span&gt;
&lt;span class="na"&gt;useDotenv&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="c1"&gt;## Package individually each function&lt;/span&gt;
&lt;span class="na"&gt;package&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;individually&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="c1"&gt;## Apply plugins&lt;/span&gt;
&lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;serverless-python-requirements&lt;/span&gt; &lt;span class="c1"&gt;#install python requirements&lt;/span&gt;

&lt;span class="c1"&gt;## Define provider and globals&lt;/span&gt;
&lt;span class="na"&gt;provider&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;aws&lt;/span&gt;
  &lt;span class="na"&gt;runtime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python3.12&lt;/span&gt;

&lt;span class="c1"&gt;## Define atomic functions&lt;/span&gt;
&lt;span class="na"&gt;functions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;## Weather function&lt;/span&gt;
  &lt;span class="na"&gt;weather&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;src/agent/weather/handler.weather&lt;/span&gt; &lt;span class="c1"&gt;#function handler&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;package&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;#package patterns&lt;/span&gt;
      &lt;span class="na"&gt;include&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;!**/*"&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;src/agent/weather/**&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key configuration highlights:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;provider&lt;/code&gt;: Defines AWS as the deployment target and uses Python 3.12 as the runtime.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;functions.weather&lt;/code&gt;: Specifies the Lambda function to deploy and exposes it via a public URL (&lt;code&gt;url: true&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To deploy your function (you should have &lt;code&gt;AWS credentials&lt;/code&gt; setup on your machine), run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sls deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After a successful deployment, your AI powered weather agent will be accessible online, ready to take prompts and return real-time forecasts. 🌤️&lt;/p&gt;

&lt;h2&gt;
  
  
  🐍 Stay with Py, use CDK
&lt;/h2&gt;

&lt;p&gt;If you prefer using only &lt;code&gt;python&lt;/code&gt; and have a fully python repo, you can use &lt;code&gt;AWS CDK&lt;/code&gt; for your infrastructure as code, here is an example.&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;aws_cdk&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Stack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;aws_lambda&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;_lambda&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;constructs&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Construct&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WeatherAgentStack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Stack&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Construct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;construct_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="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="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;construct_id&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="c1"&gt;# Lambda function
&lt;/span&gt;        &lt;span class="n"&gt;weather_function&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;WeatherFunction&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;_lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PYTHON_3_12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;handler.weather&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;_lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Code&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_asset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;src/agent/weather&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Enable Function URL (public)
&lt;/span&gt;        &lt;span class="n"&gt;weather_function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_function_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;auth_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;_lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FunctionUrlAuthType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NONE&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nc"&gt;WeatherAgentStack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ServerlessStrandsWeatherAgent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;synth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  ❓ API Gateway, Lambda URL and distributed architectures
&lt;/h2&gt;

&lt;p&gt;By default, many serverless projects expose functions via &lt;code&gt;Amazon API Gateway&lt;/code&gt;, but in this case we did not use it for a specific reason:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Amazon API Gateway&lt;/code&gt; has a hard timeout limit of 30 seconds. This means if your agent takes longer than that (e.g., due to a slow model call or network delay), the request will be terminated. &lt;strong&gt;&lt;a href="https://aws.amazon.com/about-aws/whats-new/2024/06/amazon-api-gateway-integration-timeout-limit-29-seconds/?nc1=h_ls" rel="noopener noreferrer"&gt;You can request a limit increase from AWS&lt;/a&gt;&lt;/strong&gt;, specifically provided for AI and LLM use cases, but that may not be suitable for all use cases for &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/limits.html#api-gateway-execution-service-limits-table" rel="noopener noreferrer"&gt;its side effects&lt;/a&gt;. For simple, fast responses, anyway, API Gateway might be fine.&lt;/li&gt;
&lt;li&gt;Instead, you can use &lt;code&gt;Lambda Function URL&lt;/code&gt;, which supports &lt;strong&gt;response streaming&lt;/strong&gt; and avoids the 30-second cap. If your agent generates partial responses over time or needs longer to compute, streaming is often a better choice. For LLM agents &lt;code&gt;AWS Lambda URLs&lt;/code&gt; with streaming are often the better option. &lt;strong&gt;Please pay attention to the security best practices as AWS Lambda URL do not offer security patterns like Custom Authorizer or Cognito Integration, here you should implement them in the lambda itself!&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Moreover, as we are talking about an agent you should probably integrate it in a wider distributed architecture knowing you can directly invoke lambda passing an event formatted as the test one and secure it with IAM and least privilege permissions&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To dive deeper into the topic, &lt;a href="https://medium.com/theburningmonk-com/when-to-use-api-gateway-vs-lambda-function-urls-47a3157b0069" rel="noopener noreferrer"&gt;check out this excellent article&lt;/a&gt; by the &lt;a href="https://www.linkedin.com/in/theburningmonk/" rel="noopener noreferrer"&gt;AWS Serverless Hero Yan Cui&lt;/a&gt;: a must-read if you're working with &lt;code&gt;AWS Lambda&lt;/code&gt; and trying to decide between &lt;code&gt;Amazon API Gateway&lt;/code&gt; and &lt;code&gt;AWS Lambda Function URLs&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.serverless.com/blog/aws-lambda-function-urls-with-serverless-framework" rel="noopener noreferrer"&gt;Another great article is this one&lt;/a&gt; by the &lt;a href="https://www.linkedin.com/in/matthieunapoli/" rel="noopener noreferrer"&gt;AWS Serverless Hero Mattieu Napoli&lt;/a&gt; comparing &lt;code&gt;AWS Lambda Function URls&lt;/code&gt; and &lt;code&gt;Amazon API Gateway&lt;/code&gt; specifically for &lt;code&gt;Serverless Framework&lt;/code&gt; use cases.&lt;/p&gt;

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

&lt;p&gt;&lt;code&gt;Strands Agents SDK&lt;/code&gt; is surprisingly fun to work with. It removes a lot of the boilerplate from typical agent frameworks, and it’s designed to be hackable. Whether you’re building a dev assistant, customer support bot, or something more chaotic, this is a solid starting point.&lt;/p&gt;

&lt;p&gt;Curious about what you can do with Strands Agents SDK? Check out this awesome &lt;a href="https://community.aws/content/2xOwSRPn2OwV2nj6ZYyTxn6P7gH/building-ai-agents-with-strands-an-introduction-to-the-series" rel="noopener noreferrer"&gt;hands-on series&lt;/a&gt; by &lt;a href="https://www.linkedin.com/in/dennis-traub/" rel="noopener noreferrer"&gt;Dennis Traub&lt;/a&gt;: it inspired me to give it a try! You should definitely read it to master the full potential of Strands Agents SDK.&lt;/p&gt;

&lt;h2&gt;
  
  
  ⏭️ Next Step
&lt;/h2&gt;

&lt;p&gt;Have you heard about &lt;a href="https://modelcontextprotocol.io/introduction" rel="noopener noreferrer"&gt;Model Context Protocol (MCP)&lt;/a&gt;?&lt;br&gt;
If not, you can dive deep on how to build agents which could be plugged to any client implementing this protocol &lt;a href="https://dev.to/aws-builders/deploy-a-minimal-mcp-server-on-aws-lambda-with-serverless-framework-3e42"&gt;in my previous series&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You can combine this approach with Strands Agents SDK!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://strandsagents.com/0.1.x/user-guide/concepts/tools/mcp-tools/" rel="noopener noreferrer"&gt;Also there is an entire section of the doc on MCP&lt;/a&gt;. &lt;strong&gt;I'll probably continue this series implementing an MCP server with &lt;code&gt;Strands Agents&lt;/code&gt; on &lt;code&gt;AWS Lambda&lt;/code&gt;.&lt;/strong&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  🙋 Who am I
&lt;/h2&gt;

&lt;p&gt;I'm &lt;a href="https://www.linkedin.com/in/desiodavide" rel="noopener noreferrer"&gt;D. De Sio&lt;/a&gt; and I work as a Head of Software Engineering in &lt;a href="https://eleva.it/" rel="noopener noreferrer"&gt;Eleva&lt;/a&gt;.&lt;br&gt;
I'm currently (Apr 2025) an &lt;a href="https://www.credly.com/badges/9929fdf2-7a3d-4013-9de6-57c80e4920b9/public_url" rel="noopener noreferrer"&gt;AWS Certified Solution Architect Professional&lt;/a&gt; and &lt;a href="https://www.credly.com/badges/8c5a1487-191b-429e-8c2d-7cee43bf316b/public_url" rel="noopener noreferrer"&gt;AWS Certified DevOps Engineer Professional&lt;/a&gt;, but also a &lt;a href="https://www.linkedin.com/company/aws-user-group-pavia/" rel="noopener noreferrer"&gt;User Group Leader (in Pavia)&lt;/a&gt;, an &lt;strong&gt;AWS Community Builder&lt;/strong&gt; and, last but not least, a #serverless enthusiast.&lt;/p&gt;

&lt;p&gt;My work in this field is to advocate about serverless and help as more dev teams to adopt it, as well as customers break their monolith into API and micro-services using it.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>ai</category>
      <category>strands</category>
      <category>serverless</category>
    </item>
    <item>
      <title>🚀 Let's use SAM: rebuilding our minimal serverless MCP server</title>
      <dc:creator>Davide De Sio</dc:creator>
      <pubDate>Mon, 28 Apr 2025 06:59:27 +0000</pubDate>
      <link>https://dev.to/aws-builders/lets-use-sam-rebuilding-our-minimal-serverless-mcp-server-4k99</link>
      <guid>https://dev.to/aws-builders/lets-use-sam-rebuilding-our-minimal-serverless-mcp-server-4k99</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;: &lt;a href="https://github.com/eleva/sam-serverless-mcp-server" rel="noopener noreferrer"&gt;Go to this repo for the SAM template&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;: &lt;a href="https://github.com/eleva/serverless-mcp-cli" rel="noopener noreferrer"&gt;Go to this repo for a CLI to start with your next serverless MCP server&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Hey devs 👋, if you saw my first two post in this series about &lt;a href="https://dev.to/aws-builders/deploy-a-minimal-mcp-server-on-aws-lambda-with-serverless-framework-3e42"&gt;building a minimal Model Context Protocol server with AWS Lambda using the Serverless Framework&lt;/a&gt;, this is the natural follow-up for those who prefer using &lt;a href="https://github.com/aws/aws-cdk" rel="noopener noreferrer"&gt;AWS Serverless Application Model (SAM)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The post on how to deploy an MCP server in a serverless environment was particularly well received and even got featured in two outstanding newsletters: &lt;a href="https://serverlessadvocate.substack.com/p/33-resilient-solutions" rel="noopener noreferrer"&gt;Serverless Developer Advocate #33&lt;/a&gt; by &lt;a href="https://www.linkedin.com/in/lee-james-gilmore/" rel="noopener noreferrer"&gt;Lee Gilmore&lt;/a&gt; and &lt;a href="https://www.readysetcloud.io/newsletter/160/" rel="noopener noreferrer"&gt;Ready, Set, Cloud #160&lt;/a&gt; by &lt;a href="https://www.linkedin.com/in/allenheltondev/" rel="noopener noreferrer"&gt;Allen Helton&lt;/a&gt;. I highly recommend subscribing to both as they’re packed with insights and inspiration for serverless enthusiasts!&lt;/p&gt;

&lt;p&gt;Also the. second post of this series was cited in &lt;a href="https://serverlessadvocate.substack.com/p/34-mcp-everywhere" rel="noopener noreferrer"&gt;Serverless Developer Advocate #34&lt;/a&gt; by Lee Gilmore. I can't be happier about this community feedback.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why SAM?
&lt;/h2&gt;

&lt;p&gt;In my latest post I've shown to you this omparison table between &lt;code&gt;Serverless Framework&lt;/code&gt;, &lt;code&gt;AWS SAM&lt;/code&gt; and &lt;code&gt;AWS CKD&lt;/code&gt;. Again: read it carefully before choosing your preferred way to do IaC in your project.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature / Tool&lt;/th&gt;
&lt;th&gt;Serverless Framework&lt;/th&gt;
&lt;th&gt;AWS SAM (Serverless Application Model)&lt;/th&gt;
&lt;th&gt;AWS CDK (Cloud Development Kit)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ownership&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;V3 independent (deprecated), V4 enterprise, &lt;a href="https://github.com/oss-serverless/serverless" rel="noopener noreferrer"&gt;OSS alternative to V4&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;AWS&lt;/td&gt;
&lt;td&gt;AWS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Abstraction Level&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;High-level&lt;/td&gt;
&lt;td&gt;Medium-level&lt;/td&gt;
&lt;td&gt;Low to medium-level&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Language&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;YAML + plugins (JavaScript/TS)&lt;/td&gt;
&lt;td&gt;YAML + some scripting&lt;/td&gt;
&lt;td&gt;TypeScript, Python, Java, C#, Go&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cloud Provider Support&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Multi-cloud (AWS, Azure, GCP, etc)&lt;/td&gt;
&lt;td&gt;AWS only&lt;/td&gt;
&lt;td&gt;AWS only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Template Syntax&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Custom syntax (serverless.yml)&lt;/td&gt;
&lt;td&gt;CloudFormation-compatible YAML&lt;/td&gt;
&lt;td&gt;Imperative (code-based)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Local Development&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Good support via plugins&lt;/td&gt;
&lt;td&gt;Good (via &lt;code&gt;sam local&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Limited, depends on constructs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Deployment&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;CLI-driven&lt;/td&gt;
&lt;td&gt;CLI-driven (&lt;code&gt;sam deploy&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;CLI-driven (&lt;code&gt;cdk deploy&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;State Management&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Built-in via &lt;code&gt;.serverless&lt;/code&gt; folder&lt;/td&gt;
&lt;td&gt;CloudFormation&lt;/td&gt;
&lt;td&gt;CloudFormation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Extensibility&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;High (plugins, hooks)&lt;/td&gt;
&lt;td&gt;Moderate (some hooks/plugins)&lt;/td&gt;
&lt;td&gt;High (custom constructs, reusable code)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Maturity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Very mature&lt;/td&gt;
&lt;td&gt;Mature&lt;/td&gt;
&lt;td&gt;Rapidly growing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Best For&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Multi-cloud serverless apps&lt;/td&gt;
&lt;td&gt;Simple AWS Lambda apps&lt;/td&gt;
&lt;td&gt;Complex infrastructure-as-code on AWS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Learning Curve&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Low to moderate&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Moderate to high&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Testing/Debugging&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Plugin-based&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sam local invoke/start-api&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Manual / unit tests on code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CI/CD Integration&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Easy (via plugins or custom)&lt;/td&gt;
&lt;td&gt;Easy (via CodePipeline or custom)&lt;/td&gt;
&lt;td&gt;Easy (via CodePipeline or custom)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;V3 Free, &lt;a href="https://www.serverless.com/pricing" rel="noopener noreferrer"&gt;V4 pricing&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  So, why pick AWS SAM over Serverless Framework and CDK?
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;SAM is built and maintained by AWS&lt;/strong&gt;. That means native integration for services like Lambda, API Gateway, DynamoDB, and &lt;strong&gt;integrations with CloudFormation&lt;/strong&gt;, CloudWatch, and CodeDeploy out of the box. No need for external plugins or workarounds to make things “just work.”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Designed only for serverless, unlike CDK, which is general purpose infrastructure as code&lt;/strong&gt;. As an example: In CDK, to create an API Gateway connected to a Lambda function, you’ll write TypeScript or Python code that explicitly defines routes, integrations, permissions, and deploy stages, in SAM this is really simple with a couple of line of code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local dev &amp;amp; testing is very powerful: SAM CLI lets you run Lambda functions locally and mock API Gateway events&lt;/strong&gt;. This feels more like traditional dev workflows, which is something CDKdon’t handle as smoothly, while Serverless Framework require serverless offline plugin.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simpler learning curve than CDK&lt;/strong&gt;, as CDK is powerful but verbose. You’re writing imperative code to describe declarative infrastructure. That’s cool, but not always necessary for serverless apps. SAM keeps things simple, YAML based, and readable across different teams: devs, ops, or whoever knows it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So if you're building pure serverless apps on AWS: SAM it’s native, lightweight, and focused on serverless. CDK shines when you’re managing AWS infrastructure, but when your app is 90% serverless API based on Lambda, SAM is just way faster to learn.&lt;/p&gt;

&lt;h2&gt;
  
  
  📦 What’s Inside the Repo
&lt;/h2&gt;

&lt;p&gt;This project spins up as the previous one:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An &lt;code&gt;AWS Lambda&lt;/code&gt; function hosting a &lt;strong&gt;serverless MCP server&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;An &lt;code&gt;Amazon API Gateway&lt;/code&gt; with a &lt;code&gt;POST&lt;/code&gt; &lt;code&gt;/mcp&lt;/code&gt; route&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🛠️ Features
&lt;/h2&gt;

&lt;p&gt;Our goal is always to have a skeleton to deploy our MCP server in a serverless environment, but using &lt;code&gt;AWS SAM&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simple MCP server with just a few lines of code&lt;/li&gt;
&lt;li&gt;Runs in a single AWS Lambda function&lt;/li&gt;
&lt;li&gt;HTTP POST endpoint at /mcp&lt;/li&gt;
&lt;li&gt;Local development&lt;/li&gt;
&lt;li&gt;Comes with a basic “add” tool (yeps, just adds two numbers via JSON-RPC: here you should put your endpoint logic!)&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;We add &lt;code&gt;template.yml&lt;/code&gt;, &lt;code&gt;samconfig.toml&lt;/code&gt; and &lt;code&gt;buildspec.yml&lt;/code&gt; respectively as our resource template, configuration (useful for deploy) and CI/CD pipiline build phase.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sam-serverless-mcp-server/
├── __tests__/              # Jest tests
├── src/                    # Source code
│   └── index.js                # MCP server handler
├── .gitignore              # Git ignore file
├── buildspec.yml           # Buildspec file for AWS CodeBuild and CodePipeline (CI/CD)
├── jest.config.mjs         # Jest config file
├── package.json            # Project dependencies
├── package-lock.json       # Project lock file
├── README.md               # This documentation file
├── samconfig.toml          # Serverless Application Model config
└── template.yml            # Serverless Application Model template
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🏗️ SAM code
&lt;/h2&gt;

&lt;p&gt;You can easily read the code following comments in the template file: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;set a timeout&lt;/li&gt;
&lt;li&gt;use &lt;code&gt;nodejs22.x&lt;/code&gt; as runtime&lt;/li&gt;
&lt;li&gt;spin up an &lt;code&gt;AWS::Serverless::Function&lt;/code&gt; giving the proper handler path&lt;/li&gt;
&lt;li&gt;create an API Gateway with a &lt;code&gt;POST&lt;/code&gt; route on &lt;code&gt;/mcp&lt;/code&gt; path, all automatically setting in &lt;code&gt;Events&lt;/code&gt; attribute an event of type &lt;code&gt;Api&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: serverless-mcp-server

Globals:
  Function:
    Timeout: 29
    Runtime: nodejs22.x

Resources:
  McpServerFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: src/index.handler
      Events:
        McpApi:
          Type: Api
          Properties:
            Path: /mcp
            Method: post
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tips: if you want to switch to API Gateway V2, just change the type to &lt;code&gt;HttpApi&lt;/code&gt; and you're good to go.&lt;/p&gt;

&lt;h2&gt;
  
  
  🚀 Getting Started
&lt;/h2&gt;

&lt;p&gt;To get it up and running follow those steps.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install dependencies:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Run Locally with SAM
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sam &lt;span class="nb"&gt;local &lt;/span&gt;start-api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Local endpoint will be available at:&lt;br&gt;
POST &lt;code&gt;http://localhost:3000/mcp&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  🧪 Test with jest
&lt;/h2&gt;

&lt;p&gt;There are some basic tests included in the &lt;code&gt;__tests__&lt;/code&gt; folder. You can run them with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  📡 Deploy to AWS
&lt;/h2&gt;

&lt;p&gt;Follow those steps.&lt;/p&gt;

&lt;p&gt;Build with SAM&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sam build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally deploy (before that you should configure AWS Credentials with aws-cli)&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;After deployment, the MCP server will be live at the URL output by the command.&lt;/p&gt;

&lt;h2&gt;
  
  
  🧪 Locally or once deployed, test also with curl requests
&lt;/h2&gt;

&lt;h3&gt;
  
  
  List tools
&lt;/h3&gt;

&lt;p&gt;Change &lt;code&gt;your-endpoint&lt;/code&gt; with the one noted after deploy or with &lt;code&gt;localhost:3000&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;curl &lt;span class="nt"&gt;--location&lt;/span&gt; &lt;span class="s1"&gt;'https://your-endpoint/dev/mcp'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'content-type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'accept: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'jsonrpc: 2.0'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'{
  "jsonrpc": "2.0",
  "method": "tools/list",
  "id": 1
}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ➕ Use the add Tool
&lt;/h3&gt;

&lt;p&gt;Change &lt;code&gt;your-endpoint&lt;/code&gt; with the one noted.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--location&lt;/span&gt; &lt;span class="s1"&gt;'https://your-endpoint/dev/mcp'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'content-type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'accept: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'jsonrpc: 2.0'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "add",
    "arguments": {
      "a": 5,
      "b": 3
    }
  }
}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  💎 Kickstart your next serverless MCP project with a handy CLI
&lt;/h2&gt;

&lt;p&gt;A little gem to help you kickstart your next MCP serverless projects on AWS: I created a &lt;a href="https://github.com/eleva/serverless-mcp-cli" rel="noopener noreferrer"&gt;CLI&lt;/a&gt; that lets you choose between the three boilerplates from this series (Serverless Framework, AWS CDK, or AWS SAM). It’s built with &lt;a href="https://oclif.io/" rel="noopener noreferrer"&gt;oclif&lt;/a&gt; and is easy to install.&lt;/p&gt;

&lt;p&gt;It allows you to choose the framework with just a command "&lt;code&gt;serverless-mcp-cli init&lt;/code&gt;" and automatically installs the dependencies so you are ready to go.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  ⏭️ Next Step
&lt;/h2&gt;

&lt;p&gt;I'm planning to continue this series:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;integrate authentication&lt;/li&gt;
&lt;li&gt;integrate state management&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🙋 Who am I
&lt;/h2&gt;

&lt;p&gt;I'm &lt;a href="https://www.linkedin.com/in/desiodavide" rel="noopener noreferrer"&gt;D. De Sio&lt;/a&gt; and I work as a Head of Software Engineering in &lt;a href="https://eleva.it/" rel="noopener noreferrer"&gt;Eleva&lt;/a&gt;.&lt;br&gt;
I'm currently (Apr 2025) an &lt;a href="https://www.credly.com/badges/9929fdf2-7a3d-4013-9de6-57c80e4920b9/public_url" rel="noopener noreferrer"&gt;AWS Certified Solution Architect Professional&lt;/a&gt; and &lt;a href="https://www.credly.com/badges/8c5a1487-191b-429e-8c2d-7cee43bf316b/public_url" rel="noopener noreferrer"&gt;AWS Certified DevOps Engineer Professional&lt;/a&gt;, but also a &lt;a href="https://www.linkedin.com/company/aws-user-group-pavia/" rel="noopener noreferrer"&gt;User Group Leader (in Pavia)&lt;/a&gt;, an &lt;strong&gt;AWS Community Builder&lt;/strong&gt; and, last but not least, a #serverless enthusiast.&lt;/p&gt;

&lt;p&gt;My work in this field is to advocate about serverless and help as more dev teams to adopt it, as well as customers break their monolith into API and micro-services using it.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>🚀 From Serverless Framework to AWS CDK: rebuilding our minimal serverless MCP server</title>
      <dc:creator>Davide De Sio</dc:creator>
      <pubDate>Wed, 23 Apr 2025 12:10:42 +0000</pubDate>
      <link>https://dev.to/aws-builders/from-serverless-framework-to-aws-cdk-rebuilding-our-minimal-serverless-mcp-server-1232</link>
      <guid>https://dev.to/aws-builders/from-serverless-framework-to-aws-cdk-rebuilding-our-minimal-serverless-mcp-server-1232</guid>
      <description>&lt;p&gt;Hey devs 👋, if you saw my last post about &lt;a href="https://dev.to/aws-builders/deploy-a-minimal-mcp-server-on-aws-lambda-with-serverless-framework-3e42"&gt;building a minimal Model Context Protocol server with AWS Lambda using the Serverless Framework&lt;/a&gt;, this is the natural follow-up for those who prefer using &lt;a href="https://github.com/aws/aws-cdk" rel="noopener noreferrer"&gt;AWS CDK&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The post on how to deploy an MCP server in a serverless environment was particularly well received and even got featured in two outstanding newsletters: &lt;a href="https://serverlessadvocate.substack.com/p/33-resilient-solutions" rel="noopener noreferrer"&gt;Serverless Developer Advocate #33&lt;/a&gt; by &lt;a href="https://www.linkedin.com/in/lee-james-gilmore/" rel="noopener noreferrer"&gt;Lee Gilmore&lt;/a&gt; and &lt;a href="https://www.readysetcloud.io/newsletter/160/" rel="noopener noreferrer"&gt;Ready, Set, Cloud #160&lt;/a&gt; by &lt;a href="https://www.linkedin.com/in/allenheltondev/" rel="noopener noreferrer"&gt;Allen Helton&lt;/a&gt;. I highly recommend subscribing to both as they’re packed with insights and inspiration for serverless enthusiasts!&lt;/p&gt;

&lt;h2&gt;
  
  
  🤔 Why Use CDK
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;AWS CDK&lt;/code&gt; gives us fine-grained control over infrastructure, which makes it a great option if you’re scaling things up later.&lt;/p&gt;

&lt;p&gt;Here’s the repo if you want to jump in: 👉 &lt;a href="https://github.com/eleva/cdk-serverless-mcp-server" rel="noopener noreferrer"&gt;cdk-serverless-mcp-server&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The best thing about AWS CDK is that &lt;strong&gt;lets you define infrastructure in actual code (&lt;code&gt;TypeScript&lt;/code&gt;, &lt;code&gt;Python&lt;/code&gt;, etc.), which can feel more natural as dev than writing &lt;code&gt;YAML&lt;/code&gt; or &lt;code&gt;JSON&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you're:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Planning to integrate with other AWS services&lt;/li&gt;
&lt;li&gt;Wanting more programmatic control&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Or just curious about how to do Infrastructure as Code with CDK without YAML/JSON files&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;…you should give it a try!&lt;/p&gt;

&lt;p&gt;Here is a comparison table between &lt;code&gt;Serverless Framework&lt;/code&gt;, &lt;code&gt;AWS SAM&lt;/code&gt; and &lt;code&gt;AWS CKD&lt;/code&gt;. Read it carefully before choosing your preferred way to do IaC in your project.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature / Tool&lt;/th&gt;
&lt;th&gt;Serverless Framework&lt;/th&gt;
&lt;th&gt;AWS SAM (Serverless Application Model)&lt;/th&gt;
&lt;th&gt;AWS CDK (Cloud Development Kit)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ownership&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;V3 independent (deprecated), V4 enterprise, &lt;a href="https://github.com/oss-serverless/serverless" rel="noopener noreferrer"&gt;OSS alternative to V4&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;AWS&lt;/td&gt;
&lt;td&gt;AWS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Abstraction Level&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;High-level&lt;/td&gt;
&lt;td&gt;Medium-level&lt;/td&gt;
&lt;td&gt;Low to medium-level&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Language&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;YAML + plugins (JavaScript/TS)&lt;/td&gt;
&lt;td&gt;YAML + some scripting&lt;/td&gt;
&lt;td&gt;TypeScript, Python, Java, C#, Go&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cloud Provider Support&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Multi-cloud (AWS, Azure, GCP, etc)&lt;/td&gt;
&lt;td&gt;AWS only&lt;/td&gt;
&lt;td&gt;AWS only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Template Syntax&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Custom syntax (serverless.yml)&lt;/td&gt;
&lt;td&gt;CloudFormation-compatible YAML&lt;/td&gt;
&lt;td&gt;Imperative (code-based)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Local Development&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Good support via plugins&lt;/td&gt;
&lt;td&gt;Good (via &lt;code&gt;sam local&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Limited, depends on constructs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Deployment&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;CLI-driven&lt;/td&gt;
&lt;td&gt;CLI-driven (&lt;code&gt;sam deploy&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;CLI-driven (&lt;code&gt;cdk deploy&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;State Management&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Built-in via &lt;code&gt;.serverless&lt;/code&gt; folder&lt;/td&gt;
&lt;td&gt;CloudFormation&lt;/td&gt;
&lt;td&gt;CloudFormation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Extensibility&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;High (plugins, hooks)&lt;/td&gt;
&lt;td&gt;Moderate (some hooks/plugins)&lt;/td&gt;
&lt;td&gt;High (custom constructs, reusable code)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Maturity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Very mature&lt;/td&gt;
&lt;td&gt;Mature&lt;/td&gt;
&lt;td&gt;Rapidly growing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Best For&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Multi-cloud serverless apps&lt;/td&gt;
&lt;td&gt;Simple AWS Lambda apps&lt;/td&gt;
&lt;td&gt;Complex infrastructure-as-code on AWS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Learning Curve&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Low to moderate&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Moderate to high&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Testing/Debugging&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Plugin-based&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sam local invoke/start-api&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Manual / unit tests on code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CI/CD Integration&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Easy (via plugins or custom)&lt;/td&gt;
&lt;td&gt;Easy (via CodePipeline or custom)&lt;/td&gt;
&lt;td&gt;Easy (via CodePipeline or custom)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;V3 Free, &lt;a href="https://www.serverless.com/pricing" rel="noopener noreferrer"&gt;V4 pricing&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  📦 What’s Inside the Repo
&lt;/h2&gt;

&lt;p&gt;This project spins up as the previous one:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An &lt;code&gt;AWS Lambda&lt;/code&gt; function hosting a &lt;strong&gt;serverless MCP server&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;An &lt;code&gt;Amazon API Gateway&lt;/code&gt; with a &lt;code&gt;POST&lt;/code&gt; &lt;code&gt;/mcp&lt;/code&gt; route&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our goal is always to have a skeleton to deploy our MCP server in a serverless environment, but using &lt;code&gt;AWS CDK&lt;/code&gt;.&lt;/p&gt;

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

&lt;p&gt;We add a &lt;code&gt;bin&lt;/code&gt; and &lt;code&gt;lib&lt;/code&gt; folder for our CDK app and stack.&lt;br&gt;
Also note the &lt;code&gt;cdk.json&lt;/code&gt; to define our project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cdk-serverless-mcp-server/
├── __tests__/                              # Jest tests
├── bin/                                    # CDK entry point
├── cdk-serverless-mcp-server.ts                # CDK app
├── lib/                                    # CDK stack
│   └── cdk-serverless-mcp-server-stack.ts      # CDK stack
├── src/                                    # Source code
│   └── index.mjs                               # MCP server handler
├── .gitignore                              # Git ignore file
├── cdk.json                                # CDK project config
├── package.json                            # Project dependencies
├── package-lock.json                       # Project lock file
├── README.md                               # This documentation file
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🚀 Getting Started
&lt;/h2&gt;

&lt;p&gt;To get it up and running follow those steps.&lt;/p&gt;

&lt;p&gt;Install dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install AWS CDK globally (if not already installed):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; aws-cdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test locally with jest&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🏗️ CDK code
&lt;/h2&gt;

&lt;p&gt;You can easily read the code following comments in the stack file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Stack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;StackProps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Duration&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-cdk-lib&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Construct&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;constructs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-cdk-lib/aws-lambda&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;apigateway&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-cdk-lib/aws-apigateway&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;LayerVersion&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aws-cdk-lib/aws-lambda&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CdkServerlessMcpServerStack&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Stack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Construct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;StackProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Initialize the stack&lt;/span&gt;

        &lt;span class="c1"&gt;// Define runtime&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;runtime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODEJS_22_X&lt;/span&gt;

        &lt;span class="c1"&gt;// Create dependencies lambda layer&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dependenciesLayerName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dependencies-layer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Name of the layer&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dependenciesLayerFolder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;layer/dependencies&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Folder containing the layer code&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dependenciesLayerDesc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Layer containing project dependencies&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Description of the layer&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dependenciesLayerProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Code&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromAsset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dependenciesLayerFolder&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// Path to the layer code&lt;/span&gt;
            &lt;span class="na"&gt;compatibleRuntimes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nx"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;// Specify the compatible runtimes&lt;/span&gt;
            &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dependenciesLayerDesc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Description of the layer&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dependenciesLayer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;LayerVersion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dependenciesLayerName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dependenciesLayerProps&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Create lambda function&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mcpLambda&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;McpHandler&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// The runtime environment for the Lambda function&lt;/span&gt;
            &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;index.handler&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// The name of the exported function in our code&lt;/span&gt;
            &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Code&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromAsset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;src&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// Path to our lambda function code&lt;/span&gt;
            &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;seconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;29&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// Set timeout to 29 seconds&lt;/span&gt;
            &lt;span class="na"&gt;layers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;dependenciesLayer&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;// Add the layer to the lambda function&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="c1"&gt;// Create API Gateway&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;apigateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;RestApi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;McpApi&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;restApiName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MCP Service&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// The name of the API&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="c1"&gt;// Add a resource and method to the API Gateway&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mcpResource&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mcp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Create a resource named 'mcp'&lt;/span&gt;
        &lt;span class="nx"&gt;mcpResource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addMethod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;apigateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LambdaIntegration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mcpLambda&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// Add a POST method to the resource&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What’s happening here?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Node.js 22&lt;/code&gt; support: just set the runtime to &lt;code&gt;NODEJS_22_X&lt;/code&gt; and you're good to go.&lt;/li&gt;
&lt;li&gt;Custom &lt;code&gt;Lambda Layer&lt;/code&gt;: to avoid bloating your function zip and to improve reusability, we package our dependencies into a Lambda Layer (&lt;code&gt;layer/dependencies&lt;/code&gt;). This also helps with cold starts! &lt;/li&gt;
&lt;li&gt;A &lt;code&gt;Lambda&lt;/code&gt; function with &lt;code&gt;MCP server&lt;/code&gt; code&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;API Gateway&lt;/code&gt; integration: we expose the Lambda via API Gateway, setting up a &lt;code&gt;POST&lt;/code&gt; method on the &lt;code&gt;/mcp&lt;/code&gt; endpoint in just a few lines.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  📡 Deploy to AWS
&lt;/h2&gt;

&lt;p&gt;Follow those steps.&lt;/p&gt;

&lt;p&gt;Before all, install dependencies for the layer&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run layer-dependencies-install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should bootstrap the CDK (just one time on the account):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cdk bootstrap
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, deploy the stack:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;After deployment, the MCP server will be live at the URL output by the command.&lt;/p&gt;

&lt;p&gt;Here is how it looks like when deploying:&lt;/p&gt;

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

&lt;p&gt;Take a moment to copy the endpoint shown after running the command.&lt;/p&gt;

&lt;h2&gt;
  
  
  🧪 Once deployed, test with curl requests
&lt;/h2&gt;

&lt;h3&gt;
  
  
  List tools
&lt;/h3&gt;

&lt;p&gt;Change &lt;code&gt;your-endpoint&lt;/code&gt; with the one noted.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--location&lt;/span&gt; &lt;span class="s1"&gt;'https://your-endpoint/dev/mcp'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'content-type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'accept: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'jsonrpc: 2.0'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'{
  "jsonrpc": "2.0",
  "method": "tools/list",
  "id": 1
}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is an example (response linted with &lt;a href="https://jqlang.org/" rel="noopener noreferrer"&gt;jq&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%2F32iv5oi15y4p81bx2apk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F32iv5oi15y4p81bx2apk.png" alt=" " width="800" height="535"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  ➕ Use the add Tool
&lt;/h3&gt;

&lt;p&gt;Change &lt;code&gt;your-endpoint&lt;/code&gt; with the one noted.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--location&lt;/span&gt; &lt;span class="s1"&gt;'https://your-endpoint/dev/mcp'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'content-type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'accept: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'jsonrpc: 2.0'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "add",
    "arguments": {
      "a": 5,
      "b": 3
    }
  }
}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is an example (response linted with &lt;a href="https://jqlang.org/" rel="noopener noreferrer"&gt;jq&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%2Fqi6b8ixevielwhbt64rn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqi6b8ixevielwhbt64rn.png" alt=" " width="800" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  ⏭️ Next Step
&lt;/h2&gt;

&lt;p&gt;I'm planning to continue this series giving an example with SAM! Let me know if this is needed by you in the comments!&lt;/p&gt;

&lt;h2&gt;
  
  
  🙋 Who am I
&lt;/h2&gt;

&lt;p&gt;I'm &lt;a href="https://www.linkedin.com/in/desiodavide" rel="noopener noreferrer"&gt;D. De Sio&lt;/a&gt; and I work as a Head of Software Engineering in &lt;a href="https://eleva.it/" rel="noopener noreferrer"&gt;Eleva&lt;/a&gt;.&lt;br&gt;
I'm currently (Apr 2025) an &lt;a href="https://www.credly.com/badges/9929fdf2-7a3d-4013-9de6-57c80e4920b9/public_url" rel="noopener noreferrer"&gt;AWS Certified Solution Architect Professional&lt;/a&gt; and &lt;a href="https://www.credly.com/badges/8c5a1487-191b-429e-8c2d-7cee43bf316b/public_url" rel="noopener noreferrer"&gt;AWS Certified DevOps Engineer Professional&lt;/a&gt;, but also a &lt;a href="https://www.linkedin.com/company/aws-user-group-pavia/" rel="noopener noreferrer"&gt;User Group Leader (in Pavia)&lt;/a&gt;, an &lt;strong&gt;AWS Community Builder&lt;/strong&gt; and, last but not least, a #serverless enthusiast.&lt;/p&gt;

&lt;p&gt;My work in this field is to advocate about serverless and help as more dev teams to adopt it, as well as customers break their monolith into API and micro-services using it.&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>aws</category>
      <category>cdk</category>
      <category>mcp</category>
    </item>
    <item>
      <title>⚡ Deploy a minimal MCP Server on AWS Lambda with Serverless Framework ⚡</title>
      <dc:creator>Davide De Sio</dc:creator>
      <pubDate>Tue, 15 Apr 2025 19:49:28 +0000</pubDate>
      <link>https://dev.to/aws-builders/deploy-a-minimal-mcp-server-on-aws-lambda-with-serverless-framework-3e42</link>
      <guid>https://dev.to/aws-builders/deploy-a-minimal-mcp-server-on-aws-lambda-with-serverless-framework-3e42</guid>
      <description>&lt;p&gt;Hey devs 👋, after &lt;a href="https://modelcontextprotocol.io/specification/2025-03-26/basic/transports" rel="noopener noreferrer"&gt;recent MCP's protocol update (26/03/2025)&lt;/a&gt; introducing stateless HTTP request/response transport, I’ve been exploring ways to host a &lt;code&gt;Model Context Protocol (MCP)&lt;/code&gt; server using &lt;code&gt;AWS Lambda&lt;/code&gt;, and I thought I'd share the result with the community. &lt;/p&gt;

&lt;p&gt;This article is for anyone curious about deploying an &lt;code&gt;MCP-compatible serverless endpoint&lt;/code&gt;: whether you're just starting out with AWS or already using it and &lt;code&gt;Serverless Framework&lt;/code&gt; for other projects.&lt;/p&gt;

&lt;p&gt;Here’s the GitHub repo if you want to dive in right away: 👉 &lt;a href="https://github.com/eleva/serverless-mcp-server" rel="noopener noreferrer"&gt;serverless-mcp-server&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🧩 What’s this about?
&lt;/h2&gt;

&lt;p&gt;This project is a minimal implementation of an MCP server running entirely on &lt;code&gt;AWS Lambda&lt;/code&gt;, exposed via &lt;code&gt;API Gateway&lt;/code&gt; (V1 REST endpoints), and managed with &lt;code&gt;Serverless Framework&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It’s based on the awesome work from &lt;a href="https://www.linkedin.com/in/frederic-barthelet/" rel="noopener noreferrer"&gt;Frédéric Barthelet&lt;/a&gt;, who created &lt;a href="https://github.com/fredericbarthelet/middy-mcp" rel="noopener noreferrer"&gt;middy-mcp&lt;/a&gt;, a convenient middleware,  that makes developing MCP server with Lambda easy as pie. This middleware is based on &lt;a href="https://github.com/middyjs/middy" rel="noopener noreferrer"&gt;middy&lt;/a&gt;, a middleware engine for AWS Lambda you should definitely know.&lt;/p&gt;

&lt;p&gt;My goal is to give you a skeleton to spin up an MCP server that you can test locally and deploy easily in production, all in a few commands (that should be familiar if you are familiar with Node.js projects).&lt;/p&gt;

&lt;h2&gt;
  
  
  🛠️ Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Simple MCP server with just a few lines of code&lt;/li&gt;
&lt;li&gt;Runs in a single AWS Lambda function&lt;/li&gt;
&lt;li&gt;HTTP POST endpoint at /mcp&lt;/li&gt;
&lt;li&gt;Local development support with &lt;code&gt;serverless-offline&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Comes with a basic “add” tool (yeps, just adds two numbers via JSON-RPC: here you should put your endpoint logic!)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🧑‍💻 Quickstart
&lt;/h2&gt;

&lt;p&gt;Let’s get it running locally first.&lt;br&gt;
You should have &lt;a href="https://nodejs.org/en/download" rel="noopener noreferrer"&gt;Node&lt;/a&gt; installed (you may also use &lt;a href="https://github.com/nvm-sh/nvm" rel="noopener noreferrer"&gt;nvm&lt;/a&gt; or &lt;a href="https://hub.docker.com/_/node/" rel="noopener noreferrer"&gt;docker&lt;/a&gt;).&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install dependencies:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;Install &lt;a href="https://github.com/oss-serverless/serverless" rel="noopener noreferrer"&gt;open source severless&lt;/a&gt; globally (if you haven't already installed):
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; osls
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;Run Locally with &lt;code&gt;serverless-offline&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sls offline
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;🎉 Great! You’ll now have a local endpoint at:&lt;br&gt;
&lt;code&gt;POST&lt;/code&gt; &lt;code&gt;http://localhost:3000/dev/mcp&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  🧪 Try it Out (with curl)
&lt;/h2&gt;
&lt;h3&gt;
  
  
  List tools
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--location&lt;/span&gt; &lt;span class="s1"&gt;'http://localhost:3000/dev/mcp'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'content-type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'accept: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'jsonrpc: 2.0'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'{
  "jsonrpc": "2.0",
  "method": "tools/list",
  "id": 1
}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  ➕ Use the add Tool
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--location&lt;/span&gt; &lt;span class="s1"&gt;'http://localhost:3000/dev/mcp'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'content-type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'accept: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'jsonrpc: 2.0'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "add",
    "arguments": {
      "a": 5,
      "b": 3
    }
  }
}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You should get a response with the sum (8)&lt;/p&gt;
&lt;h2&gt;
  
  
  🧪 Test (with Jest)
&lt;/h2&gt;

&lt;p&gt;There are some basic tests included in the &lt;code&gt;__tests__&lt;/code&gt; folder. You can run them with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🚀 Deploy to AWS
&lt;/h2&gt;

&lt;p&gt;Once you’re happy with it locally, deploy to AWS with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sls deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://www.serverless.com/framework/docs/providers/aws/guide/credentials" rel="noopener noreferrer"&gt;You should configure AWS credentials.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Serverless Framework&lt;/code&gt; will handle the creation of the Lambda, API Gateway, and all the necessary wiring.&lt;/p&gt;

&lt;p&gt;After deploy, you’ll get a live HTTP endpoint where you can run the same curl calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  ⚡ Serverless file
&lt;/h2&gt;

&lt;p&gt;Required serverless file to deploy our serverless-mcp-server is very simple. We just use latest stable of Node on AWS Lambda &lt;code&gt;nodejs22.x&lt;/code&gt; and create a &lt;code&gt;POST&lt;/code&gt; endpoint on path &lt;code&gt;/mcp&lt;/code&gt;. That's all folks!&lt;/p&gt;

&lt;p&gt;Be aware: you shouldn't go in production with this minimal setup! You should secure your api with best practices for authorization and security! If you are not familiar with those, please read &lt;a href="https://dev.to/ddesio/superpower-rest-api-dx-with-serverless-and-devops-best-practices-with-aws-51f6"&gt;this blog post&lt;/a&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;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;serverless-mcp-server&lt;/span&gt;
&lt;span class="na"&gt;frameworkVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3"&lt;/span&gt;

&lt;span class="na"&gt;provider&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;aws&lt;/span&gt;
  &lt;span class="na"&gt;runtime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nodejs22.x&lt;/span&gt;
  &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;

&lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;serverless-offline&lt;/span&gt;

&lt;span class="na"&gt;functions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;mcpServer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;src/index.handler&lt;/span&gt;
    &lt;span class="na"&gt;events&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mcp&lt;/span&gt;
          &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;post&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;I built this mainly as a starting point for experimenting with MCP in serverless environment, see the awesome work of &lt;a href="https://www.linkedin.com/in/frederic-barthelet/" rel="noopener noreferrer"&gt;Frédéric Barthelet&lt;/a&gt; in action and I hope it helps some of you do the same!&lt;/p&gt;

&lt;p&gt;If you have ideas for improvements, feel free to open an issue or PR. Also, I’d love to hear what you're building with MCP and serverless.&lt;/p&gt;

&lt;h2&gt;
  
  
  ⏭️ Next Step
&lt;/h2&gt;

&lt;p&gt;I'm planning to continue this series giving an example with SAM and CDK. Let me know if this is needed by you in the comments!&lt;/p&gt;

&lt;h2&gt;
  
  
  🙋 Who am I
&lt;/h2&gt;

&lt;p&gt;I'm &lt;a href="https://www.linkedin.com/in/desiodavide" rel="noopener noreferrer"&gt;D. De Sio&lt;/a&gt; and I work as a Head of Software Engineering in &lt;a href="https://eleva.it/" rel="noopener noreferrer"&gt;Eleva&lt;/a&gt;.&lt;br&gt;
I'm currently (Apr 2025) an &lt;a href="https://www.credly.com/badges/9929fdf2-7a3d-4013-9de6-57c80e4920b9/public_url" rel="noopener noreferrer"&gt;AWS Certified Solution Architect Professional&lt;/a&gt; and &lt;a href="https://www.credly.com/badges/8c5a1487-191b-429e-8c2d-7cee43bf316b/public_url" rel="noopener noreferrer"&gt;AWS Certified DevOps Engineer Professional&lt;/a&gt;, but also a &lt;a href="https://www.linkedin.com/company/aws-user-group-pavia/" rel="noopener noreferrer"&gt;User Group Leader (in Pavia)&lt;/a&gt;, an &lt;strong&gt;AWS Community Builder&lt;/strong&gt; and, last but not least, a #serverless enthusiast.&lt;/p&gt;

&lt;p&gt;My work in this field is to advocate about serverless and help as more dev teams to adopt it, as well as customers break their monolith into API and micro-services using it.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>mcp</category>
      <category>serverless</category>
    </item>
  </channel>
</rss>
