<?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>Forem: Eric D Johnson</title>
    <description>The latest articles on Forem by Eric D Johnson (@edjgeek).</description>
    <link>https://forem.com/edjgeek</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%2F2745619%2F51d975f9-b5eb-4ca5-897d-239f41bffb39.jpg</url>
      <title>Forem: Eric D Johnson</title>
      <link>https://forem.com/edjgeek</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/edjgeek"/>
    <language>en</language>
    <item>
      <title>Serverless with Mama J — Why Serverless</title>
      <dc:creator>Eric D Johnson</dc:creator>
      <pubDate>Fri, 22 May 2026 06:00:00 +0000</pubDate>
      <link>https://forem.com/aws/serverless-with-mama-j-why-serverless-1978</link>
      <guid>https://forem.com/aws/serverless-with-mama-j-why-serverless-1978</guid>
      <description>&lt;p&gt;If you ever want to test how well you really understand something, try explaining it to your mom. That's exactly what I got to do with serverless.&lt;/p&gt;

&lt;p&gt;My mom has been my biggest fan and supporter my entire technical career. A few weeks ago she told me she watches every one of my YouTube videos about serverless and wanted to know — in plain English — what it actually is and why it matters. Just so you know, my mom isn't a developer, but she's pretty tech-savvy. I thought it would be fun to record the whole conversation, and you can watch it &lt;a href="https://www.youtube.com/watch?v=vg1Q1to4qoE" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/vg1Q1to4qoE"&gt;
  &lt;/iframe&gt;
&lt;br&gt;
I also turned that chat into this blog so anyone who's new to development (or just curious) can follow along easily. So if that's you — welcome! I hope this helps.&lt;/p&gt;

&lt;p&gt;Oh, and everyone calls her Mama J… so you can too. Let's dive in!&lt;/p&gt;

&lt;h2&gt;
  
  
  Serverless starts with servers
&lt;/h2&gt;

&lt;p&gt;Yup, this is the worst-kept secret in all of serverless: it still runs on servers. So, before we talk about serverless, we need to understand what a server actually is and what it does.&lt;/p&gt;

&lt;p&gt;The clue is right in the name — a server serves. And what does it serve? Files. That's it. Every website and every web app you've ever used is really just a bunch of different files being delivered to you. Those files are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML" rel="noopener noreferrer"&gt;HTML (Hypertext Markup Language)&lt;/a&gt;&lt;/strong&gt; – the content and basic structure of the page&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS" rel="noopener noreferrer"&gt;CSS (Cascading Style Sheets)&lt;/a&gt;&lt;/strong&gt; – the design and layout&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript" rel="noopener noreferrer"&gt;JavaScript&lt;/a&gt;&lt;/strong&gt; – what makes the page interactive&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Media&lt;/strong&gt; – photos, videos, music, and all the other visual goodies&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The client
&lt;/h3&gt;

&lt;p&gt;To talk to a server, Mama J needs a client. A client is anything that can send a request to the server and then show (or use) what the server sends back.&lt;/p&gt;

&lt;p&gt;Most of the time, that client is a web browser on her computer, tablet, or phone. But it doesn't have to be! For example, the Amazon shopping app on her phone is also a client. So is the Alexa voice service on her Echo device — when she says "Alexa, order more Diet Dr. Pepper," Alexa acts as the client, talks to Amazon's servers, and gets the job done.&lt;/p&gt;

&lt;p&gt;The client's job is always the same: it takes the information the server returns and turns it into something Mama J can see or use.&lt;/p&gt;

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

&lt;p&gt;Here's what happens when Mama J wants to buy her son a birthday gift on Amazon.com:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;She opens her browser and types Amazon.com.&lt;/li&gt;
&lt;li&gt;The request gets routed to one of Amazon's many servers.&lt;/li&gt;
&lt;li&gt;The server processes the request.&lt;/li&gt;
&lt;li&gt;The server sends back the right HTML, CSS, JavaScript, and media so her browser can build the page.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The processing
&lt;/h3&gt;

&lt;p&gt;That little phrase in step 3 — "the server processes the request" — is where the magic happens. This is where the server applies what we call business logic.&lt;/p&gt;

&lt;p&gt;Think of business logic as the "brain" or the special rules that make the website actually useful for Mama J. It's the part that decides what to show her and how to make the experience personal.&lt;/p&gt;

&lt;p&gt;For example, when Mama J visits Amazon, the site doesn't just show the same boring homepage to everyone. Thanks to business logic, it remembers she was looking at birthday gifts for her son last week, so it puts a big "Recommended for you" section right at the top with more toys, wrapping paper, and even a suggestion for the perfect card. It also knows she loves Diet Dr. Pepper, so it quietly adds a "Buy it again" button for her favorite 12-pack. None of that happens by accident — the server ran its rules: "If this is Mama J, show her the stuff she actually cares about."&lt;/p&gt;

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

&lt;p&gt;The same thing happens on Facebook. When she logs in, the business logic looks at who she follows, what she's liked before, and even how long she usually spends reading family posts. Then it builds her entire news feed so the first thing she sees is a photo of her grandkids, not some random political rant. It's all custom-made just for her.&lt;/p&gt;

&lt;p&gt;How does any of this work? Because she logged in, so the system knows it's Mama J. The server then runs its business logic — basically a set of smart instructions that say, "If this is Mama J, show her her stuff, in the order she'll like best." That's business logic in plain English.&lt;/p&gt;

&lt;h2&gt;
  
  
  The challenge of running servers
&lt;/h2&gt;

&lt;p&gt;Servers are incredible — they power everything from video games to generative AI. But running them comes with some serious headaches.&lt;/p&gt;

&lt;h3&gt;
  
  
  Security and maintenance
&lt;/h3&gt;

&lt;p&gt;Servers need constant care. New threats appear every single day. If your server is open to the internet, you have to be an expert in secure networking, operating-system patching, hardware upgrades, monitoring, and a dozen other things. You can't just set it and forget it — it demands 24/7 attention.&lt;/p&gt;

&lt;h3&gt;
  
  
  Availability and scalability
&lt;/h3&gt;

&lt;p&gt;Websites have to stay up 24 hours a day, 7 days a week. That means you need at least two servers so one can cover for the other if something breaks. In reality, big sites run on thousands of servers spread across different geographic areas. They also use auto-scaling to spin up more servers when traffic spikes and scale back down when it drops.&lt;/p&gt;

&lt;h3&gt;
  
  
  Paying for idle services
&lt;/h3&gt;

&lt;p&gt;All of that means you're paying for servers to sit there running… even when nobody's using them. Mama J nailed it when she said, "It's like keeping a restaurant fully staffed day and night, whether you have customers or not."&lt;/p&gt;

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

&lt;p&gt;That's what we call idle time — and idle time is expensive.&lt;/p&gt;

&lt;p&gt;So Mama J asked the million-dollar question: "Why don't we just call the staff in when the customers actually show up?"&lt;/p&gt;

&lt;p&gt;Great question, Mom. That's exactly what serverless does.&lt;/p&gt;

&lt;h2&gt;
  
  
  The serverless approach
&lt;/h2&gt;

&lt;p&gt;At AWS we've been running Amazon.com at massive global scale for over 30 years, so we know a thing or two about servers. We looked at how companies were struggling with all the work it takes to keep servers running and decided we could make it simpler.&lt;/p&gt;

&lt;h3&gt;
  
  
  What we set out to build
&lt;/h3&gt;

&lt;p&gt;We wanted to build something that would let developers run their code at any scale without having to manage servers, security patches, scaling, or any of the other stuff that's the same for every company. We call that "&lt;a href="https://aws.amazon.com/blogs/aws/we_build_muck_s/" rel="noopener noreferrer"&gt;undifferentiated heavy lifting&lt;/a&gt;."&lt;/p&gt;

&lt;p&gt;We wanted developers to focus on their ideas instead of infrastructure. We wanted the system to handle huge traffic spikes automatically, and we wanted them to pay only for the time their code was actually running — basically, wake everything up only when customers show up.&lt;/p&gt;

&lt;h3&gt;
  
  
  AWS Lambda
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/lambda/" rel="noopener noreferrer"&gt;AWS Lambda&lt;/a&gt; is a service that runs your code without you managing any servers. You write your code, deploy it to Lambda, and it takes care of the infrastructure — servers, networking, security, and scaling.&lt;/p&gt;

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

&lt;p&gt;When a request comes in, here's what happens:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lambda spins up a small execution environment to run your code&lt;/li&gt;
&lt;li&gt;Your business logic does its thing&lt;/li&gt;
&lt;li&gt;The response goes back to the user&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first time this happens, Lambda has to set everything up from scratch — that's called a &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtime-environment.html" rel="noopener noreferrer"&gt;cold start&lt;/a&gt;. After that, the environment hangs around for a while, ready for the next request. If another request comes in while it's still warm, it reuses that same environment — a warm start — and it's faster. But if things go quiet for too long, Lambda cleans it up and the next request starts fresh again.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pricing
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://aws.amazon.com/lambda/pricing/" rel="noopener noreferrer"&gt;pricing&lt;/a&gt; model is pay-per-use. You're charged based on the number of requests and how long each one runs. No requests, no charge.&lt;/p&gt;

&lt;p&gt;For example, a website handling 1 million requests a month — each taking half a second with 512 MB of memory — would cost about $4.37 total. A traditional server running 24/7 costs $15–$30 a month, and you'd need at least two for availability, so $30–$60 a month regardless of traffic. With Lambda, a month of zero traffic costs $0.00.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scalability
&lt;/h3&gt;

&lt;p&gt;Lambda also scales automatically. By default, your AWS account gets &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-limits.html" rel="noopener noreferrer"&gt;1,000 concurrent Lambda executions&lt;/a&gt; per region — that's 1,000 separate copies of your code running at the exact same time, shared across all your Lambda functions in that region. That's a soft limit — if you need more, you can request an increase from AWS. But for most workloads, 1,000 is more than enough.&lt;/p&gt;

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

&lt;p&gt;To put that in perspective — say Mama J's website goes viral and 1,000 people all hit it at the same second. Lambda spins up 1,000 environments, one for each request, handles them all simultaneously, and sends back 1,000 responses. And since each request typically finishes in milliseconds, those 1,000 slots free up almost instantly and are ready for the next wave. In practice, that means Lambda can handle tens of thousands of requests per second without you configuring anything. With a traditional server, you'd be scrambling to spin up more machines before the traffic crushes you.&lt;br&gt;
scale&lt;/p&gt;

&lt;h3&gt;
  
  
  Right tool for the job
&lt;/h3&gt;

&lt;p&gt;"So, I should just use Lambda for everything?" Mama J asked.&lt;/p&gt;

&lt;p&gt;Not quite. Lambda is a great fit for most things — web backends, APIs, data processing, scheduled jobs, chatbots, mobile backends, and event-driven architectures. For the majority of workloads, it's where I'd start. But there are a few situations where it's not the right tool:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Long-running workloads&lt;/strong&gt; — A single Lambda invocation has a 15-minute maximum, and that applies to synchronous execution. For workloads that need to run longer — heavy video encoding, large data migrations, overnight batch jobs — you'd traditionally reach for something like &lt;a href="https://aws.amazon.com/ecs/" rel="noopener noreferrer"&gt;Amazon ECS&lt;/a&gt; or &lt;a href="https://aws.amazon.com/batch/" rel="noopener noreferrer"&gt;AWS Batch&lt;/a&gt;. However, the new &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/durable-functions.html" rel="noopener noreferrer"&gt;AWS Lambda durable functions&lt;/a&gt; feature changes the game by letting you build long-running asynchronous workflows that coordinate across multiple Lambda invocations, well beyond the 15-minute limit. It's a big step forward, but you still need to plan your architecture around it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ultra-low latency at very high throughput&lt;/strong&gt; — Some workloads need extremely predictable response times at massive scale, like high-frequency trading systems. In those cases, always-on compute can give you tighter control.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Big legacy monoliths&lt;/strong&gt; — If you've got a large, tightly coupled application, breaking it into small Lambda functions can be painful. That's more of an architecture challenge than a Lambda limitation, but it's worth knowing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The thing is, it doesn't have to be all-or-nothing. Most real-world systems use a mix — Lambda where it fits, and other services where they're a better match. But Lambda is still the default starting point for most new projects because it takes so much off your plate.&lt;/p&gt;

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

&lt;p&gt;Mama J sat back and said, "So let me make sure I've got this. Instead of buying servers, keeping them running, paying for them when nobody's using them, and worrying about security, availability, scalability, and maintenance — you just write your code and Lambda handles the rest?"&lt;/p&gt;

&lt;p&gt;Pretty much, Mom. That's serverless in a nutshell.&lt;/p&gt;

&lt;p&gt;This is just the beginning, though. Lambda on its own is powerful, but the real fun starts when you connect it to other AWS services and build event-driven applications — things that react automatically when something happens, with no servers to manage anywhere in the stack.&lt;/p&gt;

&lt;p&gt;We'll get into all of that in the next post.&lt;/p&gt;

&lt;p&gt;Stay tuned!&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>lambda</category>
      <category>beginners</category>
      <category>aws</category>
    </item>
    <item>
      <title>200,000 MCP Servers Are Exposed. Here's Why Serverless Is Safer.</title>
      <dc:creator>Eric D Johnson</dc:creator>
      <pubDate>Wed, 20 May 2026 06:00:00 +0000</pubDate>
      <link>https://forem.com/aws/200000-mcp-servers-are-exposed-heres-why-serverless-is-safer-4p0j</link>
      <guid>https://forem.com/aws/200000-mcp-servers-are-exposed-heres-why-serverless-is-safer-4p0j</guid>
      <description>&lt;p&gt;I've spent a lot of time thinking about where MCP servers should live. I work with remote MCP servers constantly and do a lot of the architecture work around them. But I also use plenty of local ones. There's a simplicity to &lt;code&gt;npx @modelcontextprotocol/server-whatever&lt;/code&gt; that's hard to argue with.&lt;/p&gt;

&lt;p&gt;Then MCP crossed 300 million SDK downloads a month in April 2026, and a few days later, OX Security published a disclosure that put a number on what I'd been turning over in my head: the most popular MCP transport has no authentication, and 200,000 servers are running it in production.&lt;/p&gt;

&lt;p&gt;That got me to finally put my thoughts together. The short version: the subprocess-spawn vulnerability that OX Security disclosed is specific to STDIO, the local transport. Remote MCP servers avoid that specific attack path, though they introduce different web and API security considerations. And if you're going to run remote MCP servers, I think serverless is the best place to do it.&lt;/p&gt;

&lt;p&gt;Let's walk through it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What OX Security found in MCP's STDIO transport
&lt;/h2&gt;

&lt;p&gt;The root cause is in MCP's STDIO transport. When an MCP client connects to a server via STDIO, it passes a &lt;code&gt;StdioServerParameters&lt;/code&gt; object to the SDK. That object contains a &lt;code&gt;command&lt;/code&gt; field and an &lt;code&gt;args&lt;/code&gt; array that tell the SDK which process to spawn.&lt;/p&gt;

&lt;p&gt;The official MCP SDKs across Python, TypeScript, Java, and Rust do not sanitize those fields before passing them to the operating system. Whatever strings arrive get executed as shell commands on the host machine.&lt;/p&gt;

&lt;p&gt;The execution sequence makes it worse. The command runs first. Then the MCP handshake tries to validate it as a legitimate server. Then the handshake fails. But the payload already ran. OX Security described this as "execute first, validate never," and that's accurate.&lt;/p&gt;

&lt;h2&gt;
  
  
  CVE-2025-49596: Browser to backdoor
&lt;/h2&gt;

&lt;p&gt;The specific CVE that got the most attention was CVE-2025-49596, a CVSS 9.4 critical vulnerability in MCP Inspector, Anthropic's official debugging tool. The Inspector runs a proxy on localhost that accepts commands from its browser UI with zero authentication.&lt;/p&gt;

&lt;p&gt;The attack chain: On macOS and Linux, browsers allow websites to make requests to &lt;code&gt;0.0.0.0&lt;/code&gt;, which the OS silently redirects to &lt;code&gt;127.0.0.1&lt;/code&gt;. A developer visits a malicious website. The site's JavaScript reaches MCP Inspector through the &lt;code&gt;0.0.0.0&lt;/code&gt; bypass. Arbitrary code runs on the developer's machine. No phishing, no suspicious downloads. Just a website visit.&lt;/p&gt;

&lt;h2&gt;
  
  
  How many MCP servers are affected
&lt;/h2&gt;

&lt;p&gt;The numbers from the OX disclosure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;200,000+ vulnerable server instances&lt;/li&gt;
&lt;li&gt;150 million+ cumulative downloads across affected packages&lt;/li&gt;
&lt;li&gt;7,000+ publicly accessible MCP servers&lt;/li&gt;
&lt;li&gt;10+ high or critical CVEs&lt;/li&gt;
&lt;li&gt;200+ affected open-source projects&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why serverless reduces the MCP risk surface
&lt;/h2&gt;

&lt;p&gt;The STDIO model is essentially "download this script, run it as a local server, trust it." The vulnerability exists because of three assumptions baked into the STDIO transport: there's a persistent process running on the host, that process has shell access, and there's no authentication layer between the client and the process.&lt;/p&gt;

&lt;p&gt;Serverless compute inverts all three.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No persistent process to hijack.&lt;/strong&gt; Lambda functions run in Firecracker microVMs, isolated execution environments that the Lambda service manages. There's no customer-managed long-running daemon process exposed to clients.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No client-controlled process spawning.&lt;/strong&gt; When Lambda serves as an MCP server, communication happens over HTTPS. The MCP Streamable HTTP transport replaces the STDIO subprocess model entirely. There's no path for a client to trigger arbitrary process execution on the host.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Auth is infrastructure, not application code.&lt;/strong&gt; STDIO local transports rely on local process trust. With Lambda, you get IAM auth on Function URLs and IAM or Cognito authorizers on API Gateway. Auth is a configuration toggle, not something you need to build yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lambda as an MCP server: build it yourself
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;IAM auth with SigV4&lt;/strong&gt; — Set your Function URL's AuthType to &lt;code&gt;AWS_IAM&lt;/code&gt; and every request requires Signature Version 4 signing. No tokens to manage, no OAuth dance, no plaintext API keys.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OAuth 2.1 for external agents&lt;/strong&gt; — Front the Lambda function with API Gateway, attach a Cognito authorizer. API Gateway validates the token before the request ever reaches your function.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The awslabs STDIO adapter&lt;/strong&gt; — Already have MCP servers built on STDIO? The &lt;code&gt;awslabs/run-model-context-protocol-servers-with-aws-lambda&lt;/code&gt; project wraps existing STDIO-based MCP servers so they run inside Lambda. The STDIO surface stays internal to the Lambda sandbox and is never exposed to the network.&lt;/p&gt;

&lt;h2&gt;
  
  
  Working examples
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/mikegc-aws/Lambda-MCP-Server" rel="noopener noreferrer"&gt;mikegc-aws/Lambda-MCP-Server&lt;/a&gt;: Native Streamable HTTP on Lambda with IAM auth&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/awslabs/run-model-context-protocol-servers-with-aws-lambda" rel="noopener noreferrer"&gt;awslabs/run-model-context-protocol-servers-with-aws-lambda&lt;/a&gt;: Official wrapper for existing STDIO servers&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/aws-samples/sample-serverless-mcp-servers" rel="noopener noreferrer"&gt;aws-samples/sample-serverless-mcp-servers&lt;/a&gt;: Collection of agent and server patterns&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  AgentCore: let AWS manage your MCP servers
&lt;/h2&gt;

&lt;p&gt;Amazon Bedrock AgentCore centralizes auth, policy enforcement, and observability into a single endpoint. The Gateway sits in front of all your MCP servers. Interceptors let you filter which tools an agent can see based on caller identity and context. AgentCore Runtime handles containerization, scaling, and session isolation — you write the server code, AgentCore runs each session in a dedicated microVM.&lt;/p&gt;

&lt;h2&gt;
  
  
  But what about local file access?
&lt;/h2&gt;

&lt;p&gt;If your MCP tools need local file access, they still need to run locally. That's a real limitation of any remote MCP server. For tools that work with shared files or project artifacts, S3 Files is an interesting middle ground — it mounts an S3 bucket as a local filesystem on your Lambda function.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run your MCP servers somewhere secure
&lt;/h2&gt;

&lt;p&gt;Of those 200,000 exposed STDIO servers, every one that could be redeployed as a remote MCP server behind authenticated infrastructure would remove itself from that count.&lt;/p&gt;

&lt;p&gt;Serverless doesn't patch the protocol. It removes the conditions the vulnerability depends on. No persistent process, no STDIO surface, and auth on every request by default.&lt;/p&gt;

&lt;p&gt;Lambda if you want control. AgentCore if you want managed. You've got options. Use them.&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>mcp</category>
      <category>security</category>
      <category>aws</category>
    </item>
    <item>
      <title>Dynamic Looping Comes to AWS SAM</title>
      <dc:creator>Eric D Johnson</dc:creator>
      <pubDate>Mon, 18 May 2026 16:20:40 +0000</pubDate>
      <link>https://forem.com/aws/dynamic-looping-comes-to-aws-sam-2k0l</link>
      <guid>https://forem.com/aws/dynamic-looping-comes-to-aws-sam-2k0l</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Update (May 22, 2026):&lt;/strong&gt; Since the initial launch, local processing of Language Extensions has moved behind an opt-in flag for compatibility. You now enable it with &lt;code&gt;--language-extensions&lt;/code&gt;, an environment variable, or a &lt;code&gt;samconfig.toml&lt;/code&gt; entry. Details in the "Enabling local processing" section below.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/serverless/sam/" rel="noopener noreferrer"&gt;AWS SAM CLI&lt;/a&gt;, the command-line tool for building and deploying serverless applications, now supports &lt;a href="https://aws.amazon.com/cloudformation/" rel="noopener noreferrer"&gt;AWS CloudFormation&lt;/a&gt; Language Extensions. The one I am most excited about is &lt;code&gt;Fn::ForEach&lt;/code&gt;, which brings dynamic looping to your YAML templates, but it's close. If you, like me, have been copy-pasting resource definitions to infinity, that stops today.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ForEach&lt;/code&gt; is the star, but it ships alongside &lt;code&gt;Length&lt;/code&gt;, &lt;code&gt;ToJsonString&lt;/code&gt;, &lt;code&gt;FindInMap&lt;/code&gt; with default values, and conditional deletion policies. All of them work across your full local SAM workflow: build, invoke, validate, package, deploy, and sync.&lt;/p&gt;

&lt;p&gt;In this post, I walk through what CloudFormation Language Extensions brings to SAM CLI, show you how each extension works, and demonstrate the full local development experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: template duplication
&lt;/h2&gt;

&lt;p&gt;To show why this matters, take a look at the following example. I have three &lt;a href="https://aws.amazon.com/lambda/" rel="noopener noreferrer"&gt;AWS Lambda&lt;/a&gt; functions, Lambda being the serverless compute service, that each handle a different endpoint on the same API. But, almost everything about them is the same. They have the same runtime, the same memory configuration, and nearly the same structure. The only differences are the name, handler, and possibly some environment variables.&lt;/p&gt;

&lt;p&gt;The template looks like this:&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;UsersFunction&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::Serverless::Function&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;Runtime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python3.11&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;users.handler&lt;/span&gt;
      &lt;span class="na"&gt;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./src&lt;/span&gt;
      &lt;span class="na"&gt;MemorySize&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;256&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;Variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;FUNCTION_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Users&lt;/span&gt;

  &lt;span class="na"&gt;OrdersFunction&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::Serverless::Function&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;Runtime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python3.11&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;orders.handler&lt;/span&gt;
      &lt;span class="na"&gt;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./src&lt;/span&gt;
      &lt;span class="na"&gt;MemorySize&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;256&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;Variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;FUNCTION_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Orders&lt;/span&gt;

  &lt;span class="na"&gt;ProductsFunction&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::Serverless::Function&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;Runtime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python3.11&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;products.handler&lt;/span&gt;
      &lt;span class="na"&gt;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./src&lt;/span&gt;
      &lt;span class="na"&gt;MemorySize&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;256&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;Variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;FUNCTION_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Products&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three resources, nearly identical, and if I need to change the memory size or add a tracing configuration, I'm making the same edit three times. The template is fragile and hard to maintain, and it only gets worse at ten or twenty functions. So what can I do about it? That's where Language Extensions come in.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are CloudFormation Language Extensions?
&lt;/h2&gt;

&lt;p&gt;CloudFormation Language Extensions is a transform (&lt;code&gt;AWS::LanguageExtensions&lt;/code&gt;) that unlocks a suite of extended intrinsic functions for your CloudFormation templates. These functions have existed in CloudFormation for a while. What's new is that SAM CLI now processes them locally for your entire development workflow, meaning you can build, invoke, and test locally before deploying.&lt;/p&gt;

&lt;p&gt;The full suite includes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Extension&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Fn::ForEach&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Iterate over a collection and generate resources for each item&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Fn::Length&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Return the length of an array&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Fn::ToJsonString&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Convert an object or array to a JSON string&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Fn::FindInMap with DefaultValue&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Look up a value in a Mappings section with a fallback when the key doesn't exist&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Conditional DeletionPolicy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Use &lt;code&gt;Fn::If&lt;/code&gt; in DeletionPolicy (e.g., Retain in prod, Delete in dev)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Conditional UpdateReplacePolicy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Use &lt;code&gt;Fn::If&lt;/code&gt; in UpdateReplacePolicy&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;To enable them, I add &lt;code&gt;AWS::LanguageExtensions&lt;/code&gt; to my template's Transform section alongside the SAM transform:&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;Transform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;AWS::LanguageExtensions&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;AWS::Serverless-2016-10-31&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that in place, I can start using &lt;code&gt;Fn::ForEach&lt;/code&gt; to solve the duplication problem I showed earlier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fn::ForEach: define once, generate many
&lt;/h2&gt;

&lt;p&gt;Take a look at the same three functions rewritten with &lt;code&gt;Fn::ForEach&lt;/code&gt;. Instead of repeating the definition three times, I define it once and let the loop generate the rest:&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;Transform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;AWS::LanguageExtensions&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;AWS::Serverless-2016-10-31&lt;/span&gt;

&lt;span class="na"&gt;Resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Fn::ForEach::Functions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Name&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Users&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Orders&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Products&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;${Name}Function&lt;/span&gt;&lt;span class="err"&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::Serverless::Function&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;Runtime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python3.11&lt;/span&gt;
          &lt;span class="na"&gt;Handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Sub&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${Name}.handler"&lt;/span&gt;
          &lt;span class="na"&gt;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./src&lt;/span&gt;
          &lt;span class="na"&gt;MemorySize&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;256&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;Variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;FUNCTION_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Sub&lt;/span&gt; &lt;span class="s"&gt;${Name}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That single definition generates three functions: &lt;code&gt;UsersFunction&lt;/code&gt;, &lt;code&gt;OrdersFunction&lt;/code&gt;, and &lt;code&gt;ProductsFunction&lt;/code&gt;. If I need to add a fourth, I add one item to the collection array. If I need to change the memory size, I change it in one place.&lt;/p&gt;

&lt;p&gt;The anatomy of &lt;code&gt;Fn::ForEach&lt;/code&gt; breaks down into four parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Loop name&lt;/strong&gt;: &lt;code&gt;Fn::ForEach::Functions&lt;/code&gt;, a unique identifier for this loop&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Iterator variable&lt;/strong&gt;: &lt;code&gt;Name&lt;/code&gt;, the variable that takes each value in turn&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Collection&lt;/strong&gt;: &lt;code&gt;[Users, Orders, Products]&lt;/code&gt;, the values to iterate over&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Template body&lt;/strong&gt;: The resource definition using &lt;code&gt;${Name}&lt;/code&gt; for substitution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That covers the basic case where all functions share the same source code. However, what happens when each function needs its own code directory?&lt;/p&gt;

&lt;h3&gt;
  
  
  Per-function code directories
&lt;/h3&gt;

&lt;p&gt;In many projects, each function lives in its own folder. &lt;code&gt;Fn::ForEach&lt;/code&gt; handles this through dynamic artifact properties, where the &lt;code&gt;CodeUri&lt;/code&gt; itself uses the loop variable:&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;Fn::ForEach::Services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Name&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Users&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Orders&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Products&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;${Name}Service&lt;/span&gt;&lt;span class="err"&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::Serverless::Function&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;Runtime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python3.11&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;index.handler&lt;/span&gt;
          &lt;span class="na"&gt;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./services/${Name}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this directory structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;services/
├── Users/index.py
├── Orders/index.py
└── Products/index.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SAM CLI builds each function from its own directory and generates Mappings sections automatically to preserve the &lt;code&gt;Fn::ForEach&lt;/code&gt; structure in the deployed template. To see this in action, I check &lt;code&gt;.aws-sam/build/template.yaml&lt;/code&gt; after a build:&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;Mappings&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;SAMCodeUriServices&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Users&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;UsersService&lt;/span&gt;
    &lt;span class="na"&gt;Orders&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;OrdersService&lt;/span&gt;
    &lt;span class="na"&gt;Products&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ProductsService&lt;/span&gt;

&lt;span class="na"&gt;Resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Fn::ForEach::Services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Name&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Users&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Orders&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Products&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;${Name}Service&lt;/span&gt;&lt;span class="err"&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::Serverless::Function&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;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;Fn::FindInMap&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SAMCodeUriServices&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Name&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;CodeUri&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;index.handler&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SAM CLI generates the &lt;code&gt;SAMCodeUriServices&lt;/code&gt; mapping so that each collection value resolves to its own build artifact. At package time, those paths become &lt;a href="https://aws.amazon.com/s3/" rel="noopener noreferrer"&gt;Amazon S3&lt;/a&gt; URIs. I don't need to manage any of this.&lt;/p&gt;

&lt;p&gt;The same pattern works for API endpoints. Let me show one more example before moving on to the other extensions.&lt;/p&gt;

&lt;h3&gt;
  
  
  API endpoints from a loop
&lt;/h3&gt;

&lt;p&gt;I can generate multiple API endpoints from a single definition by attaching an &lt;a href="https://aws.amazon.com/api-gateway/" rel="noopener noreferrer"&gt;Amazon API Gateway&lt;/a&gt; event source inside the loop:&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;Fn::ForEach::Endpoints&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Endpoint&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;users&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;products&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;orders&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;${Endpoint}Function&lt;/span&gt;&lt;span class="err"&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::Serverless::Function&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;Runtime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python3.11&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;index.handler&lt;/span&gt;
          &lt;span class="na"&gt;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./endpoints/${Endpoint}&lt;/span&gt;
          &lt;span class="na"&gt;Events&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;Api&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;Api&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;Path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Sub&lt;/span&gt; &lt;span class="s"&gt;/${Endpoint}&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;get&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I run &lt;code&gt;sam local start-api&lt;/code&gt;, and I get three working endpoints: &lt;code&gt;/users&lt;/code&gt;, &lt;code&gt;/products&lt;/code&gt;, &lt;code&gt;/orders&lt;/code&gt;, all generated from that single resource definition.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Fn::ForEach&lt;/code&gt; is the biggest addition, but the other extensions in the suite solve real problems of their own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Beyond Fn::ForEach: Length, ToJsonString, FindInMap, and more
&lt;/h2&gt;

&lt;p&gt;Each of the remaining extensions addresses a specific gap in what CloudFormation templates could express before.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fn::Length
&lt;/h3&gt;

&lt;p&gt;When I generate resources from a collection, I sometimes need to know how many items are in that collection. Maybe I'm setting a concurrency limit based on the number of services, or creating an &lt;a href="https://aws.amazon.com/cloudwatch/" rel="noopener noreferrer"&gt;Amazon CloudWatch&lt;/a&gt; alarm that scales with the fleet. Previously, I'd hardcode that number and forget to update it when the collection changed. &lt;code&gt;Fn::Length&lt;/code&gt; returns the length of an array at deploy time:&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;Parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ServiceNames&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;CommaDelimitedList&lt;/span&gt;
    &lt;span class="na"&gt;Default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api,worker,scheduler"&lt;/span&gt;

&lt;span class="na"&gt;Resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ServiceCountMetric&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::CloudWatch::Alarm&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;AlarmDescription&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Sub&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Monitoring&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;${Fn::Length(ServiceNames)}&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;services"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Fn::ToJsonString
&lt;/h3&gt;

&lt;p&gt;Lambda functions frequently need structured configuration passed as environment variables. The problem is that environment variables are strings, so I end up building JSON by hand inside &lt;code&gt;!Sub&lt;/code&gt; with escaped quotes and line breaks, and it breaks the moment someone forgets a backslash.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Fn::ToJsonString&lt;/code&gt; solves this by converting an object to a JSON string inline:&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;Environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;CONFIG&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Fn::ToJsonString&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;region&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;AWS::Region&lt;/span&gt;
        &lt;span class="na"&gt;table&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;MyTable&lt;/span&gt;
        &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2.0"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No more escaping quotes in YAML, and no more &lt;code&gt;!Sub&lt;/code&gt; gymnastics to build JSON strings. I define the object naturally and let &lt;code&gt;Fn::ToJsonString&lt;/code&gt; handle serialization. The function reads &lt;code&gt;CONFIG&lt;/code&gt; as a standard JSON string at runtime, and if I add a field, I add it to the YAML object and the serialization stays correct.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fn::FindInMap with DefaultValue
&lt;/h3&gt;

&lt;p&gt;Mappings are great for region-specific or environment-specific configuration. However, &lt;code&gt;Fn::FindInMap&lt;/code&gt; throws a hard error if the key doesn't exist. So if I add a new region or deploy to one I didn't explicitly map, the stack fails. I end up maintaining an exhaustive list of every possible key, or wrapping lookups in conditions.&lt;/p&gt;

&lt;p&gt;Now I can provide a default value that CloudFormation uses when the key isn't found:&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;Mappings&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;RegionConfig&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;us-east-1&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;BucketPrefix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;use1"&lt;/span&gt;
    &lt;span class="na"&gt;eu-west-1&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;BucketPrefix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;euw1"&lt;/span&gt;

&lt;span class="na"&gt;Resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;MyBucket&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::S3::Bucket&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;BucketName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Sub&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${Prefix}-my-app"&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Prefix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;Fn::FindInMap&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;RegionConfig&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;AWS::Region&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;BucketPrefix&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;DefaultValue&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;default"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If I deploy to &lt;code&gt;ap-southeast-1&lt;/code&gt;, no crash. I get "default" instead of a stack failure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conditional DeletionPolicy and UpdateReplacePolicy
&lt;/h3&gt;

&lt;p&gt;In a multi-environment setup, I want production &lt;a href="https://aws.amazon.com/dynamodb/" rel="noopener noreferrer"&gt;Amazon DynamoDB&lt;/a&gt; tables and S3 buckets to survive accidental stack deletions. But in dev, I want clean teardowns without orphaned resources cluttering the account. Previously, I needed separate templates or manual post-deploy steps because &lt;code&gt;DeletionPolicy&lt;/code&gt; only accepted a static string.&lt;/p&gt;

&lt;p&gt;Now it accepts intrinsic functions:&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;Conditions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;IsProd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Equals&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="nv"&gt;Environment&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;prod&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;Resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;MyTable&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::DynamoDB::Table&lt;/span&gt;
    &lt;span class="na"&gt;DeletionPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!If&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;IsProd&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Retain&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Delete&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;UpdateReplacePolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!If&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;IsProd&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Retain&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Delete&lt;/span&gt;&lt;span class="pi"&gt;]&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;TableName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Sub&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${Environment}-data"&lt;/span&gt;
      &lt;span class="na"&gt;BillingMode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PAY_PER_REQUEST&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One template handles both: production retains data on deletion, dev cleans up after itself.&lt;/p&gt;

&lt;p&gt;That covers all the extensions. The next question is how they fit into the SAM CLI workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Full SAM CLI workflow support
&lt;/h2&gt;

&lt;p&gt;Every SAM CLI command supports Language Extensions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;sam build&lt;/code&gt;&lt;/strong&gt;: Expands loops in memory, builds each generated function&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;sam local invoke&lt;/code&gt;&lt;/strong&gt;: Invoke expanded functions by name&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;sam local start-api&lt;/code&gt;&lt;/strong&gt;: Serves all generated API endpoints&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;sam validate&lt;/code&gt;&lt;/strong&gt;: Catches syntax errors and unsupported patterns locally&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;sam package&lt;/code&gt;&lt;/strong&gt;: Preserves the &lt;code&gt;Fn::ForEach&lt;/code&gt; structure with S3 URIs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;sam deploy&lt;/code&gt;&lt;/strong&gt;: Uploads your original template for CloudFormation to process&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;sam sync&lt;/code&gt;&lt;/strong&gt;: Syncs changes to the cloud, including code-only updates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;SAM CLI expands language extensions in memory for local operations because it needs to know which functions to build and invoke. But your original unexpanded template is what goes to CloudFormation. You get the full local development experience with no template modification for deployment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enabling local processing
&lt;/h3&gt;

&lt;p&gt;Local processing of language extensions is opt-in. You enable it with the &lt;code&gt;--language-extensions&lt;/code&gt; flag:&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;span class="nt"&gt;--language-extensions&lt;/span&gt;
sam &lt;span class="nb"&gt;local &lt;/span&gt;invoke UsersFunction &lt;span class="nt"&gt;--language-extensions&lt;/span&gt; &lt;span class="nt"&gt;--event&lt;/span&gt; events/get-user.json
sam &lt;span class="nb"&gt;local &lt;/span&gt;start-api &lt;span class="nt"&gt;--language-extensions&lt;/span&gt;
&lt;span class="c"&gt;# Test your endpoints at http://localhost:3000/users&lt;/span&gt;
sam deploy &lt;span class="nt"&gt;--language-extensions&lt;/span&gt; &lt;span class="nt"&gt;--guided&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each command needs its own activation. To avoid repeating the flag, set the environment variable:&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="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;SAM_CLI_ENABLE_LANGUAGE_EXTENSIONS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
sam build
sam &lt;span class="nb"&gt;local &lt;/span&gt;invoke UsersFunction
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or persist it in your &lt;code&gt;samconfig.toml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[default.build.parameters]&lt;/span&gt;
&lt;span class="py"&gt;language_extensions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If multiple methods are set, the precedence order is: CLI flag &amp;gt; &lt;code&gt;samconfig.toml&lt;/code&gt; &amp;gt; environment variable. Before you get started, there are a few constraints worth knowing about.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations and constraints
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Collections must be locally resolvable.&lt;/strong&gt; Your &lt;code&gt;Fn::ForEach&lt;/code&gt; collection can be a static list (&lt;code&gt;[A, B, C]&lt;/code&gt;) or a parameter reference (&lt;code&gt;!Ref MyParam&lt;/code&gt;). It cannot use &lt;code&gt;Fn::GetAtt&lt;/code&gt;, &lt;code&gt;Fn::ImportValue&lt;/code&gt;, or SSM/Secrets Manager dynamic references. These require cloud API calls that SAM CLI can't make locally. The error messages are clear and suggest workarounds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Maximum 5 levels of nesting.&lt;/strong&gt; You can nest &lt;code&gt;Fn::ForEach&lt;/code&gt; loops (environments x services, for example), but CloudFormation caps it at 5 levels deep. You probably won't hit this in practice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Collection values are fixed at package time.&lt;/strong&gt; If you use a parameter-based collection with dynamic &lt;code&gt;CodeUri&lt;/code&gt;, the parameter values you use at &lt;code&gt;sam package&lt;/code&gt; must match what you use at &lt;code&gt;sam deploy&lt;/code&gt;. SAM CLI warns you when this applies.&lt;/p&gt;

&lt;p&gt;With those constraints in mind, getting started is straightforward.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get started
&lt;/h2&gt;

&lt;p&gt;This feature is available in SAM CLI v1.160.0 and later. Update and try it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--upgrade&lt;/span&gt; aws-sam-cli
sam &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Take one of your templates with duplicated resources, add the &lt;a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/transform-aws-languageextensions.html" rel="noopener noreferrer"&gt;&lt;code&gt;AWS::LanguageExtensions&lt;/code&gt;&lt;/a&gt; transform, and replace the copy-paste with &lt;code&gt;Fn::ForEach&lt;/code&gt;. If you don't have the latest CLI, the &lt;a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html" rel="noopener noreferrer"&gt;install guide&lt;/a&gt; has you covered.&lt;/p&gt;

&lt;p&gt;This has been one of the most requested features in SAM CLI history (&lt;a href="https://github.com/aws/aws-sam-cli/issues/5647" rel="noopener noreferrer"&gt;#5647&lt;/a&gt; had years of community upvotes), and the &lt;a href="https://github.com/aws/aws-sam-cli/pull/8637" rel="noopener noreferrer"&gt;implementation&lt;/a&gt; covers the full command surface. Dynamic looping in YAML, supported end-to-end. Define your resources once, generate as many as you need, and deploy with the same workflow you already know.&lt;/p&gt;

&lt;p&gt;If you run into issues or want to see what's next, the &lt;a href="https://github.com/aws/aws-sam-cli" rel="noopener noreferrer"&gt;SAM CLI repo&lt;/a&gt; is where it all happens.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>sam</category>
      <category>cloudformation</category>
    </item>
    <item>
      <title>My Brother Doesn't Code. Now He Ships Features.</title>
      <dc:creator>Eric D Johnson</dc:creator>
      <pubDate>Mon, 18 May 2026 06:00:00 +0000</pubDate>
      <link>https://forem.com/aws/my-brother-doesnt-code-now-he-ships-features-186k</link>
      <guid>https://forem.com/aws/my-brother-doesnt-code-now-he-ships-features-186k</guid>
      <description>&lt;p&gt;My brother runs a large crew of pipelayers. The math they do in the field is genuinely hard. Rolling offsets, fitting angles, engineer tape measurements in decimal feet. I don't understand any of it. But I know how to build apps, so I built him a calculator PWA.&lt;/p&gt;

&lt;p&gt;The problem came after launch. Every time he needed something changed, he'd send me a message. "Can you make the font bigger?" "The offset calculator is wrong for 22.5 degree fittings." I'd context-switch out of whatever I was doing, try to understand what he meant, get it wrong the first time, and eventually push a fix days later. I was the bottleneck, and I didn't even understand the domain. He knows what the app needs. I know how to code. But the translation layer between us is lossy.&lt;/p&gt;

&lt;p&gt;So I built an agent that cuts me out of the loop. He sends a message describing what he wants, the agent makes the changes, deploys a preview, and waits for his approval before pushing to production. No code. No git. No terminal. Just plain English and a preview link.&lt;/p&gt;

&lt;p&gt;This isn't a polished product. It's a starting point. But the decisions behind it are intentional, and I think they're worth walking through.&lt;/p&gt;

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

&lt;p&gt;I needed a chat interface that was dead simple to use from a phone in the field. Telegram's Bot API made this straightforward. You create a bot through &lt;a class="mentioned-user" href="https://dev.to/botfather"&gt;@botfather&lt;/a&gt;, get a token, register a webhook URL, and you're receiving messages as JSON payloads. No app to build, no OAuth flow, no UI framework. The user just opens a chat and starts typing.&lt;/p&gt;

&lt;p&gt;The bot becomes the entire interface. My brother doesn't need to know what's behind it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture
&lt;/h2&gt;

&lt;p&gt;Here's the full flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User sends a message to the Telegram bot&lt;/li&gt;
&lt;li&gt;Telegram POSTs the message to an API Gateway endpoint&lt;/li&gt;
&lt;li&gt;A Lambda function receives it and invokes an AgentCore Runtime&lt;/li&gt;
&lt;li&gt;The AgentCore container runs Claude with access to the codebase&lt;/li&gt;
&lt;li&gt;Claude edits code, validates, builds, and deploys a preview to S3/CloudFront&lt;/li&gt;
&lt;li&gt;The agent sends status updates and the preview URL back through Telegram&lt;/li&gt;
&lt;li&gt;User reviews the preview and says "ship it" (or asks for changes)&lt;/li&gt;
&lt;li&gt;The agent merges to main and cleans up&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Everything is defined in a single CDK stack. One &lt;code&gt;cdk deploy&lt;/code&gt; creates all the infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Claude as a Headless Developer
&lt;/h2&gt;

&lt;p&gt;I chose Claude for the agent because of the Claude Agent SDK. It gives you a headless coding experience out of the box. Claude already knows how to read files, write code, run shell commands, and iterate on errors. I didn't need to build any of that tooling. I just needed to point it at a codebase and give it constraints.&lt;/p&gt;

&lt;p&gt;The query function from the SDK is the core of it:&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;claude_agent_sdk&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ClaudeAgentOptions&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;query&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;message_text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;ClaudeAgentOptions&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;allowed_tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Read&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;Write&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;Edit&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;Bash&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;Glob&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;Grep&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;cwd&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;workspace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;max_turns&lt;/span&gt;&lt;span class="o"&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="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;hasattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;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="n"&gt;result_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;result&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 the whole invocation. Claude gets the user's message, a system prompt with context about the project, and a set of tools it can use. It figures out the rest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Guardrails Make It Safe
&lt;/h2&gt;

&lt;p&gt;Handing an AI agent the keys to a production codebase sounds risky. It is, if you don't constrain it. The system prompt is where you define what the agent can and can't do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GUARDRAILS:
1. ONLY modify files in src/ and tests/ directories.
2. ALWAYS run validation before deploying.
3. If validation fails, fix the issues and re-run until it passes.
4. NEVER deploy code that fails validation.
5. Keep changes focused — don't refactor unrelated code.
6. Preserve the engineer tape (decimal feet) convention.
7. Fitting angles are 11.25 / 22.5 / 45 / 90 degrees — don't change these.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rules 6 and 7 are domain-specific. I don't know pipelaying math, but I know those values are sacred.&lt;/p&gt;

&lt;p&gt;The workflow section tells Claude exactly how to validate, build, deploy a preview, and wait for approval:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WORKFLOW:
1. Understand the user's request. If unclear, ask for clarification.
2. Make the changes.
3. Run validation: npm run validate
4. If validation fails, fix and retry (up to 3 attempts).
5. Build and deploy to preview.
6. Share the preview URL.
7. Wait for user approval before merging.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent never pushes to main on its own. It always deploys to a preview URL first and waits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who Gets Access
&lt;/h2&gt;

&lt;p&gt;Every incoming message goes through an authorization check:&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;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;is_authorized&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;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;status&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;ignored&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;reason&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;unauthorized&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The allowlist is a comma-separated list of Telegram user IDs passed as a CloudFormation parameter at deploy time. Simple, intentional, hard to get wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  AgentCore: Ephemeral Compute with Long Runtimes
&lt;/h2&gt;

&lt;p&gt;AI coding agents are slow. They need time to read code, think, write changes, run validation, fix errors, build, and deploy. That can take minutes.&lt;/p&gt;

&lt;p&gt;Amazon Bedrock AgentCore gave me exactly what I needed. It spins up a container when a request comes in, keeps it warm for subsequent messages from the same user, and shuts it down after an idle timeout. Session affinity routes subsequent requests from the same session to the same container:&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;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="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;from&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nf"&gt;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;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;unknown&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;session_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;telegram-user-session-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="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The workspace persists. The conversation history persists. It feels stateful even though the compute is ephemeral.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Async Trick
&lt;/h2&gt;

&lt;p&gt;Telegram expects a 200 response within seconds. But the agent takes minutes. The solution is an async entrypoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@app.entrypoint&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse_telegram_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;task_id&lt;/span&gt; &lt;span class="o"&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;add_async_task&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;agent-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&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="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chat_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;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat_id&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;message&lt;/span&gt;&lt;span class="p"&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="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;threading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;_run_agent_background&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;task_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;daemon&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;start&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;status&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;accepted&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;task_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;task_id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;add_async_task&lt;/code&gt; call tells AgentCore "I'm not done yet, don't shut down this container." While the agent works, it sends status updates directly to Telegram through the Bot API.&lt;/p&gt;

&lt;h2&gt;
  
  
  One CDK Deploy
&lt;/h2&gt;

&lt;p&gt;The CDK stack creates: API Gateway endpoint, Lambda relay function, AgentCore Runtime (container built from source), S3 + CloudFront for previews, and a Secrets Manager secret for the GitHub deploy key.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;agent_runtime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;agentcore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Runtime&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;PipelayerRuntime&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_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;pipelayer_agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;agent_runtime_artifact&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;agentcore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AgentRuntimeArtifact&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="n"&gt;AGENT_DIR&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;environment_variables&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TELEGRAM_BOT_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;telegram_bot_token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value_as_string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DEV_BUCKET&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;dev_bucket_name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value_as_string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DEV_CDN_DOMAIN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;dev_distribution&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;distribution_domain_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GITHUB_DEPLOY_KEY_SECRET&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;deploy_key_secret&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;secret_arn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Real Takeaway
&lt;/h2&gt;

&lt;p&gt;AI agents work best when you constrain them. The guardrails in the system prompt are what make this safe enough to hand to someone who doesn't code. The agent can't touch files outside &lt;code&gt;src/&lt;/code&gt; and &lt;code&gt;tests/&lt;/code&gt;. It can't deploy without passing validation. It can't push to production without explicit approval.&lt;/p&gt;

&lt;p&gt;My brother doesn't need to understand React, or git, or AWS. He just needs to describe what he wants. The agent handles the rest, within boundaries I set.&lt;/p&gt;

&lt;p&gt;If you want to dig into the full implementation, the repo is at &lt;a href="https://github.com/singledigit/pipelayer-agent" rel="noopener noreferrer"&gt;github.com/singledigit/pipelayer-agent&lt;/a&gt;. It's not perfect, but it works. And my brother ships features now.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>aws</category>
      <category>serverless</category>
      <category>agentcore</category>
    </item>
    <item>
      <title>Creating Your First Lambda Function</title>
      <dc:creator>Eric D Johnson</dc:creator>
      <pubDate>Fri, 15 May 2026 06:00:00 +0000</pubDate>
      <link>https://forem.com/aws/creating-your-first-lambda-function-4a29</link>
      <guid>https://forem.com/aws/creating-your-first-lambda-function-4a29</guid>
      <description>&lt;p&gt;So you want to build your first serverless function? Let's do it. No fluff, no detours — just you, AWS SAM, and a working Lambda function running on your machine in minutes. We're not even going to deploy to AWS yet. Let's just get it working locally first.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You'll Need
&lt;/h2&gt;

&lt;p&gt;Two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;AWS SAM CLI installed&lt;/strong&gt; — SAM (Serverless Application Model) is the tool we'll use to create, build, and test our function. Follow the &lt;a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html" rel="noopener noreferrer"&gt;install guide&lt;/a&gt; for your operating system.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker or Finch installed&lt;/strong&gt; — SAM uses a container runtime to simulate Lambda locally. You have two options:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.docker.com/products/docker-desktop/" rel="noopener noreferrer"&gt;Docker Desktop&lt;/a&gt;&lt;/strong&gt; — The most common option. Install it and make sure it's running.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/runfinch/finch" rel="noopener noreferrer"&gt;Finch&lt;/a&gt;&lt;/strong&gt; — A lightweight open source alternative from AWS. Install it with &lt;code&gt;brew install finch&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Verify your setup
&lt;/h3&gt;

&lt;p&gt;Check that SAM is installed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sam &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you chose Finch, initialize and start the VM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;finch vm init
finch vm start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SAM automatically detects Finch when Docker isn't running — no extra configuration needed. If you have both installed, SAM uses Docker by default. To make Finch the default on macOS, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo&lt;/span&gt; /usr/libexec/PlistBuddy &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"Add :DefaultContainerRuntime string finch"&lt;/span&gt; /Library/Preferences/com.amazon.samcli.plist
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify your container runtime is working:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nt"&gt;--version&lt;/span&gt;   &lt;span class="c"&gt;# or: finch --version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. No AWS account needed yet. We're staying local for now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Initialize Your Project
&lt;/h2&gt;

&lt;p&gt;Open your terminal and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sam init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SAM is going to walk you through a few questions. For this blog, I'm using the TypeScript template, but you can choose any runtime and template you'd like — Python, Java, Go, whatever you're comfortable with. The prompts, project structure, and file names will vary depending on what you pick. Here's what I chose:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Template source&lt;/strong&gt; — Choose &lt;code&gt;1 - AWS Quick Start Templates&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quick start template&lt;/strong&gt; — Choose &lt;code&gt;1 - Hello World Example&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use the most popular runtime and package type?&lt;/strong&gt; — &lt;code&gt;N&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runtime&lt;/strong&gt; — Choose &lt;code&gt;12 - nodejs24.x&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Package type&lt;/strong&gt; — Choose &lt;code&gt;Zip&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Starter template&lt;/strong&gt; — Choose &lt;code&gt;2 — Hello World Example TypeScript&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enable X-Ray tracing?&lt;/strong&gt; — &lt;code&gt;N&lt;/code&gt; (we'll keep it simple for now)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enable CloudWatch Application Insights?&lt;/strong&gt; — &lt;code&gt;N&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enable Lambda Insights?&lt;/strong&gt; — &lt;code&gt;N&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Project name&lt;/strong&gt; — Hit Enter to accept the default, or name it whatever you like&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;SAM just scaffolded an entire serverless project for you. Let's look at what we got:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sam-app/
├── template.yaml          # The blueprint for your infrastructure
├── samconfig.toml         # SAM deployment configuration
├── hello-world/           # Your function code lives here
│   ├── app.ts             # Your actual Lambda function
│   ├── package.json       # Dependencies
│   └── tests/             # Unit tests
│       └── unit/
│           └── test-handler.test.ts
└── events/
    └── event.json         # A sample test event
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The two files that matter most:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;template.yaml&lt;/strong&gt; — This describes your Lambda function and any AWS resources it needs. Think of it as the blueprint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;app.ts&lt;/strong&gt; — This is your actual function code. The thing that runs when your Lambda gets invoked.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Understanding the template
&lt;/h3&gt;

&lt;p&gt;Let's look at the key part of &lt;code&gt;template.yaml&lt;/code&gt; — the function resource:&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;HelloWorldFunction&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::Serverless::Function&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;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hello-world/&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;app.lambdaHandler&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;nodejs24.x&lt;/span&gt;
      &lt;span class="na"&gt;Architectures&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;x86_64&lt;/span&gt;
      &lt;span class="na"&gt;Events&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;HelloWorld&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;Api&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;Path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/hello&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;get&lt;/span&gt;
    &lt;span class="na"&gt;Metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;BuildMethod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;esbuild&lt;/span&gt;
      &lt;span class="na"&gt;BuildProperties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Minify&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;Target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;es2020"&lt;/span&gt;
        &lt;span class="na"&gt;Sourcemap&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;EntryPoints&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;app.ts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's what's going on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Type: AWS::Serverless::Function&lt;/strong&gt; — This tells SAM to create a Lambda function. SAM handles all the underlying CloudFormation resources for you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CodeUri: hello-world/&lt;/strong&gt; — Points to the folder where your function code lives.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Handler: app.lambdaHandler&lt;/strong&gt; — Tells Lambda which file and function to run. In this case, the &lt;code&gt;lambdaHandler&lt;/code&gt; export in &lt;code&gt;app.ts&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runtime: nodejs24.x&lt;/strong&gt; — The Node.js version your function runs on.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The magic is in the &lt;strong&gt;Events&lt;/strong&gt; section. By adding an event with &lt;code&gt;Type: Api&lt;/code&gt;, SAM automatically creates an API Gateway for you — no extra configuration needed. You didn't define an API Gateway resource anywhere, but SAM sees that your function wants to respond to HTTP requests at &lt;code&gt;GET /hello&lt;/code&gt; and wires it all up behind the scenes. One line, and you've got a fully managed API endpoint in front of your Lambda.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Metadata&lt;/strong&gt; section tells SAM how to build your TypeScript code. Instead of running &lt;code&gt;tsc&lt;/code&gt; and bundling manually, SAM uses &lt;a href="https://esbuild.github.io/" rel="noopener noreferrer"&gt;esbuild&lt;/a&gt; — a JavaScript/TypeScript bundler. It compiles your TypeScript, minifies the output, generates sourcemaps for debugging, and packages it all up. You don't need to install esbuild yourself — SAM handles it during &lt;code&gt;sam build&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Build It
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;sam-app
sam build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SAM grabs your code, installs any dependencies, and packages everything up. You'll see a &lt;code&gt;.aws-sam&lt;/code&gt; folder appear — that's your build output. You don't need to touch it.&lt;/p&gt;

&lt;p&gt;Don't have the runtime installed on your machine (e.g., no Node.js)? No problem — just add &lt;code&gt;--use-container&lt;/code&gt; and SAM will build inside a Docker/Finch container instead:&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;span class="nt"&gt;--use-container&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SAM will automatically pull down a container image that matches your function's runtime, build your code inside it, and output the packaged result to &lt;code&gt;.aws-sam&lt;/code&gt; just like a normal build. You get the exact same Lambda environment without installing anything on your machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Test It Locally
&lt;/h2&gt;

&lt;p&gt;Here's the fun part. Run your Lambda function right on your machine:&lt;br&gt;
&lt;/p&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;invoke
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see a response like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"statusCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;message&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;hello world&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's your Lambda function running locally. No AWS account, no deployment, no charges. Just your code doing its thing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Make It Yours
&lt;/h3&gt;

&lt;p&gt;Let's make a quick change so you can see the full loop — edit, build, test. Open &lt;code&gt;hello-world/app.ts&lt;/code&gt; and find this line:&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;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hello world&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;Change it to:&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;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hello Lambda&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;Now rebuild and invoke again:&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
sam &lt;span class="nb"&gt;local &lt;/span&gt;invoke
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"statusCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;message&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;hello Lambda&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the workflow. Change your code, build, test — all local, all instant.&lt;/p&gt;

&lt;p&gt;Want to test it as an API instead? Run:&lt;br&gt;
&lt;/p&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;This spins up a local API Gateway. Open your browser and hit &lt;code&gt;http://127.0.0.1:3000/hello&lt;/code&gt; and you'll see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hello Lambda"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You now have a fully functional serverless API running on your laptop.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Just Happened?
&lt;/h2&gt;

&lt;p&gt;In about 5 minutes, you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scaffolded a serverless project with a single command&lt;/li&gt;
&lt;li&gt;Learned how the SAM template automatically wires up a Lambda function and API Gateway&lt;/li&gt;
&lt;li&gt;Built your TypeScript code with esbuild — no manual setup needed&lt;/li&gt;
&lt;li&gt;Ran a Lambda function locally&lt;/li&gt;
&lt;li&gt;Made a code change and saw it reflected instantly&lt;/li&gt;
&lt;li&gt;Spun up a local API endpoint and hit it from the browser&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All without touching AWS. All free. All on your machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ready to Deploy? Let's Go Live.
&lt;/h2&gt;

&lt;p&gt;You've got it working locally. Now let's put it in the cloud so the rest of the world can use it. For this part, you'll need a couple more things set up.&lt;/p&gt;

&lt;h3&gt;
  
  
  Additional Requirements
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;An AWS account&lt;/strong&gt; — Head to &lt;a href="https://aws.amazon.com/free/" rel="noopener noreferrer"&gt;aws.amazon.com/free&lt;/a&gt; and sign up. New customers get up to $200 in credits and a Free Plan that lets you explore AWS for up to 6 months at no cost. On top of that, Lambda has its own always-free tier — 1 million requests and 400,000 GB-seconds of compute per month.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AWS CLI installed and configured&lt;/strong&gt; — This is how your machine talks to your AWS account. First, install it from the &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html" rel="noopener noreferrer"&gt;AWS CLI install guide&lt;/a&gt;. Then follow the &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/getting-started-quickstart.html" rel="noopener noreferrer"&gt;Setting up the AWS CLI&lt;/a&gt; guide to configure your credentials. For most new users, you'll want the &lt;strong&gt;IAM user with short-term credentials&lt;/strong&gt; option — it's the recommended approach for getting started securely.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once you're set up, verify it's working:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws sts get-caller-identity
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you see your account ID, you're connected.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Deploy It
&lt;/h3&gt;

&lt;p&gt;From your project directory, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sam deploy &lt;span class="nt"&gt;--guided&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;--guided&lt;/code&gt; flag walks you through the deployment settings the first time. Here's what to expect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Stack Name&lt;/strong&gt; — Give it a name (like &lt;code&gt;my-sam-app&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS Region&lt;/strong&gt; — Pick your closest region (e.g., &lt;code&gt;us-east-1&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Confirm changes before deploy&lt;/strong&gt; — &lt;code&gt;Y&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Allow SAM CLI IAM role creation&lt;/strong&gt; — &lt;code&gt;Y&lt;/code&gt; (SAM needs to create a role for your function)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Disable rollback&lt;/strong&gt; — &lt;code&gt;N&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Save arguments to config file&lt;/strong&gt; — &lt;code&gt;Y&lt;/code&gt; (so you don't have to answer these again)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;SAM will show you a changeset — a preview of what it's about to create. Type &lt;code&gt;Y&lt;/code&gt; to confirm, and watch it go.&lt;/p&gt;

&lt;p&gt;When it's done, you'll see an &lt;strong&gt;Outputs&lt;/strong&gt; section with a URL. That's your live API endpoint. Copy it, paste it in your browser, and...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hello Lambda"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🎉 Your Lambda function is live on AWS.&lt;/p&gt;

&lt;h3&gt;
  
  
  Clean Up
&lt;/h3&gt;

&lt;p&gt;Don't want to leave resources hanging around? Tear it all down with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sam delete
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SAM removes everything it created. Clean slate.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Just Happened?
&lt;/h2&gt;

&lt;p&gt;In about 10 minutes, you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scaffolded a serverless project with a single command&lt;/li&gt;
&lt;li&gt;Learned how the SAM template automatically wires up a Lambda function and API Gateway&lt;/li&gt;
&lt;li&gt;Built your TypeScript code with esbuild — no manual setup needed&lt;/li&gt;
&lt;li&gt;Tested your function locally and made a code change&lt;/li&gt;
&lt;li&gt;Spun up a local API and hit it from the browser&lt;/li&gt;
&lt;li&gt;Deployed it to AWS with a real, live URL&lt;/li&gt;
&lt;li&gt;Cleaned it all up with one command&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;SAM handled all the heavy lifting — the CloudFormation stack, the IAM roles, the API Gateway, the packaging. You just answered a few questions.&lt;/p&gt;

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

&lt;p&gt;You've got the basics down. From here you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add more functions to your &lt;code&gt;template.yaml&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Connect your Lambda to DynamoDB, S3, or other AWS services&lt;/li&gt;
&lt;li&gt;Set up environment variables and custom permissions&lt;/li&gt;
&lt;li&gt;Explore event-driven architectures with SNS, SQS, and EventBridge&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Want to keep learning? Check out &lt;a href="https://serverlessland.com" rel="noopener noreferrer"&gt;Serverless Land&lt;/a&gt; — the go-to hub for all things Lambda and serverless with patterns, tutorials, and best practices. Or dive into the &lt;a href="https://youtube.com/playlist?list=PLJo-rJlep0ED198FJnTzhIB5Aut_1vDAd&amp;amp;si=vsLU45O1uH6jKyGo" rel="noopener noreferrer"&gt;Sessions with SAM&lt;/a&gt; YouTube playlist that goes deeper into everything AWS SAM can do.&lt;/p&gt;

&lt;p&gt;But that's for next time. For now, celebrate — you just went serverless. 🚀&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>lambda</category>
      <category>beginners</category>
      <category>aws</category>
    </item>
    <item>
      <title>Lambda Just Got a File System. I Put AI Agents on It.</title>
      <dc:creator>Eric D Johnson</dc:creator>
      <pubDate>Wed, 13 May 2026 15:50:28 +0000</pubDate>
      <link>https://forem.com/aws/lambda-just-got-a-file-system-i-put-ai-agents-on-it-1ej8</link>
      <guid>https://forem.com/aws/lambda-just-got-a-file-system-i-put-ai-agents-on-it-1ej8</guid>
      <description>&lt;p&gt;You've written this code before. An S3 event fires, your Lambda function wakes up, and the first thing it does is download a file to &lt;code&gt;/tmp&lt;/code&gt;. Process it. Upload the result. Clean up &lt;code&gt;/tmp&lt;/code&gt; so you don't run out of space. Repeat for every file, every invocation, every function in your pipeline.&lt;/p&gt;

&lt;p&gt;S3 Files changes that. You mount your S3 bucket as a local file system, and your Lambda code just uses &lt;code&gt;open()&lt;/code&gt;. I built a set of AI code review agents that share a workspace through a mounted S3 bucket, orchestrated by a durable function, and the file access code is the most boring part of the whole project. That's the point.&lt;/p&gt;

&lt;h2&gt;
  
  
  The /tmp Tax
&lt;/h2&gt;

&lt;p&gt;If you've built anything on Lambda that touches S3 data, you know the pattern. You need a file. S3 doesn't give you files. It gives you objects. So you download the object to &lt;code&gt;/tmp&lt;/code&gt;, do your work, and upload the result back.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# The old way: every Lambda developer has written this
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;

&lt;span class="n"&gt;s3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;s3&lt;/span&gt;&lt;span class="sh"&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;lambda_handler&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;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;bucket&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bucket&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;key&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;# Download to /tmp
&lt;/span&gt;    &lt;span class="n"&gt;local_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/tmp/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;download_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;local_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Do your actual work
&lt;/span&gt;    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;local_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Upload the result
&lt;/span&gt;    &lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put_object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Bucket&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;key&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="n"&gt;Body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Clean up so you don't fill /tmp
&lt;/span&gt;    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;local_path&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 a lot of ceremony for "read a file and write a file." And it gets worse when you have multiple functions that need to work with the same data. Each one downloads its own copy. Each one manages its own &lt;code&gt;/tmp&lt;/code&gt;. If you're processing a large repo or a dataset, you're burning through the 10GB &lt;code&gt;/tmp&lt;/code&gt; limit fast.&lt;/p&gt;

&lt;p&gt;I'd be doing you a disservice if I didn't mention the libraries that make this less painful. Tools like &lt;code&gt;s3fs&lt;/code&gt; and &lt;code&gt;smart_open&lt;/code&gt; abstract some of this away. But they're still making API calls under the hood. Your code is still talking to S3 through an SDK, not through a file system.&lt;/p&gt;

&lt;h2&gt;
  
  
  S3 Files for Lambda
&lt;/h2&gt;

&lt;p&gt;S3 Files is a new feature that mounts your S3 bucket as a local file system on your Lambda function. Your code reads and writes files at a mount path like &lt;code&gt;/mnt/workspace&lt;/code&gt;, and S3 Files handles the synchronization back to the bucket. Changes you write show up in S3 within minutes. Changes made to S3 objects appear on the file system within seconds.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# The new way: just file paths
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="n"&gt;WORKSPACE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/mnt/workspace&lt;/span&gt;&lt;span class="sh"&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;lambda_handler&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;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Read directly from the mount
&lt;/span&gt;    &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WORKSPACE&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;source&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;app.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Write directly to the mount
&lt;/span&gt;    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WORKSPACE&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;result.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;write_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No boto3 for file access. No &lt;code&gt;/tmp&lt;/code&gt; management. No upload step. The file system IS the interface.&lt;/p&gt;

&lt;p&gt;Under the hood, S3 Files is built on Amazon EFS. It delivers sub-millisecond latency for actively used data by caching your working set on high-performance storage. For large sequential reads, it streams directly from S3. You get file system semantics with S3 durability and economics.&lt;/p&gt;

&lt;p&gt;Here's the thing, though. S3 Files requires a VPC. Your Lambda function needs to be in the same VPC as the mount targets, and you need a NAT gateway for outbound internet access.&lt;/p&gt;

&lt;p&gt;I'll be honest: as a serverless guy, I generally avoid VPCs. But AWS has removed most of the hurdles over the years. VPC-attached Lambda functions no longer have the cold start penalty they used to. The networking setup is boilerplate you write once. And for what S3 Files gives you, the tradeoff is worth it. Get yourself a reusable network template and move on.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We're Building
&lt;/h2&gt;

&lt;p&gt;I wanted to test S3 Files with something more interesting than "read a CSV." So I built a serverless code review system. You point it at a public GitHub repo, and three things happen:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A durable orchestrator function clones the repo to a shared S3 Files workspace&lt;/li&gt;
&lt;li&gt;A security review agent and a style review agent analyze the code in parallel&lt;/li&gt;
&lt;li&gt;The results land in the same workspace as JSON files, synced back to S3&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All three Lambda functions mount the same S3 bucket. The orchestrator writes files. The agents read them. No S3 keys passed between functions. No downloading to &lt;code&gt;/tmp&lt;/code&gt;. The file system is the coordination layer.&lt;/p&gt;

&lt;p&gt;The agents use the Strands Agents SDK with Amazon Bedrock. Each agent gets custom file tools that operate on the mount path, and Claude decides which files to read, what to analyze, and what to write. The orchestrator uses Lambda durable functions to coordinate the workflow with automatic checkpointing.&lt;/p&gt;

&lt;p&gt;The full source is on GitHub: &lt;a href="https://github.com/singledigit/lambda-s3-files-example" rel="noopener noreferrer"&gt;singledigit/lambda-s3-files-example&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The SAM Template
&lt;/h2&gt;

&lt;p&gt;The IaC is the part that took the most iteration. S3 Files is brand new, and the CloudFormation resource types aren't in the linter yet. Here's what I learned.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Resource Chain
&lt;/h3&gt;

&lt;p&gt;You need five resources to get S3 Files working with Lambda:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;S3 Bucket&lt;/strong&gt; with versioning enabled (required)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IAM Role&lt;/strong&gt; for S3 Files to access the bucket&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;S3 Files FileSystem&lt;/strong&gt; that bridges the bucket to NFS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mount Targets&lt;/strong&gt; in each AZ (network endpoints)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Access Point&lt;/strong&gt; that controls POSIX identity for Lambda&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The resource types are &lt;code&gt;AWS::S3Files::FileSystem&lt;/code&gt;, &lt;code&gt;AWS::S3Files::MountTarget&lt;/code&gt;, and &lt;code&gt;AWS::S3Files::AccessPoint&lt;/code&gt;. Your IDE's CloudFormation linter won't recognize them yet. Ignore the red squiggles.&lt;/p&gt;

&lt;h3&gt;
  
  
  The IAM Role Gotcha
&lt;/h3&gt;

&lt;p&gt;The S3 Files IAM role trusts &lt;code&gt;elasticfilesystem.amazonaws.com&lt;/code&gt;, not &lt;code&gt;s3files.amazonaws.com&lt;/code&gt;. This tripped me up. S3 Files is built on EFS, so the trust relationship goes through the EFS service principal.&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;S3FilesRole&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::IAM::Role&lt;/span&gt;
  &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/service-role/&lt;/span&gt;
    &lt;span class="na"&gt;AssumeRolePolicyDocument&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2012-10-17'&lt;/span&gt;
      &lt;span class="na"&gt;Statement&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Sid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AllowS3FilesAssumeRole&lt;/span&gt;
          &lt;span class="na"&gt;Effect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Allow&lt;/span&gt;
          &lt;span class="na"&gt;Principal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;Service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;elasticfilesystem.amazonaws.com&lt;/span&gt;
          &lt;span class="na"&gt;Action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sts:AssumeRole&lt;/span&gt;
          &lt;span class="na"&gt;Condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;StringEquals&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;aws:SourceAccount&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;AWS::AccountId&lt;/span&gt;
            &lt;span class="na"&gt;ArnLike&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;aws:SourceArn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Sub&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;arn:aws:s3files:${AWS::Region}:${AWS::AccountId}:file-system/*'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The role needs S3 permissions to read and write the bucket. Scope it to your specific bucket ARN with &lt;code&gt;aws:ResourceAccount&lt;/code&gt; conditions.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Access Point
&lt;/h3&gt;

&lt;p&gt;This is the important part for Lambda. The access point controls the POSIX identity your function runs as and creates a writable root directory. Without it, Lambda can mount the file system but can't write to it.&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;S3FilesAccessPoint&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::S3Files::AccessPoint&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;FileSystemId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;S3FileSystem.FileSystemId&lt;/span&gt;
    &lt;span class="na"&gt;PosixUser&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Uid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1000'&lt;/span&gt;
      &lt;span class="na"&gt;Gid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1000'&lt;/span&gt;
    &lt;span class="na"&gt;RootDirectory&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;/lambda&lt;/span&gt;
      &lt;span class="na"&gt;CreationPermissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;OwnerUid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1000'&lt;/span&gt;
        &lt;span class="na"&gt;OwnerGid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1000'&lt;/span&gt;
        &lt;span class="na"&gt;Permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;755'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;CreationPermissions&lt;/code&gt; property is crucial. It auto-creates the &lt;code&gt;/lambda&lt;/code&gt; directory with the right ownership when a client first connects. Without it, the root directory is owned by root (UID 0), and Lambda (running as UID 1000 through the access point) can't create subdirectories.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lambda Configuration
&lt;/h3&gt;

&lt;p&gt;On the Lambda side, &lt;code&gt;FileSystemConfigs&lt;/code&gt; takes the access point ARN (not the file system ARN) and a local mount path:&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;OrchestratorFunction&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::Serverless::Function&lt;/span&gt;
  &lt;span class="na"&gt;DependsOn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MountTargetA&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MountTargetB&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;FileSystemConfigs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Arn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;S3FilesAccessPoint.AccessPointArn&lt;/span&gt;
        &lt;span class="na"&gt;LocalMountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/mnt/workspace&lt;/span&gt;
    &lt;span class="na"&gt;VpcConfig&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;SecurityGroupIds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;NetworkingStack.Outputs.LambdaSGId&lt;/span&gt;
      &lt;span class="na"&gt;SubnetIds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;NetworkingStack.Outputs.PrivateSubnetAId&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;NetworkingStack.Outputs.PrivateSubnetBId&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;DependsOn&lt;/code&gt; on the mount targets is important. Lambda can't mount the file system until the mount targets are available, and they take about five minutes to create.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd Do Differently
&lt;/h2&gt;

&lt;p&gt;S3 Files is genuinely good for this use case. Shared file access between Lambda functions without the ceremony of S3 API calls. But a few things to know:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Consistency model matters.&lt;/strong&gt; S3 Files provides close-to-open consistency. If Function A writes a file and Function B reads it immediately, B might not see the latest version. For my use case, the orchestrator writes first and the agents run after, so ordering is natural. If you need real-time coordination between concurrent writers, you'll want a different pattern.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;VPC adds complexity.&lt;/strong&gt; Not much, but some. You need subnets, security groups, NAT gateway for internet access. Template it once and reuse it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cold starts are fine.&lt;/strong&gt; VPC-attached Lambda functions used to add 10+ seconds of cold start. That's been fixed for years. My functions cold-start in under 2 seconds with the file system mount.&lt;/p&gt;

&lt;p&gt;The full code is at &lt;a href="https://github.com/singledigit/lambda-s3-files-example" rel="noopener noreferrer"&gt;github.com/singledigit/lambda-s3-files-example&lt;/a&gt;. Clone it, deploy it, point it at a repo. The agents will tell you what they think.&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>aws</category>
      <category>lambda</category>
      <category>ai</category>
    </item>
    <item>
      <title>Modern Container Builds and WebSocket APIs Come to AWS SAM</title>
      <dc:creator>Eric D Johnson</dc:creator>
      <pubDate>Tue, 05 May 2026 18:09:06 +0000</pubDate>
      <link>https://forem.com/aws/modern-container-builds-and-websocket-apis-come-to-aws-sam-8gm</link>
      <guid>https://forem.com/aws/modern-container-builds-and-websocket-apis-come-to-aws-sam-8gm</guid>
      <description>&lt;p&gt;SAM CLI has always been good at taking the grunt work out of serverless deployments. You define your functions and APIs in a template, and SAM handles the CloudFormation, the packaging, the deployment. It works.&lt;/p&gt;

&lt;p&gt;Until recently, two things were missing. BuildKit support for container image builds. And a SAM resource type for WebSocket APIs.&lt;/p&gt;

&lt;p&gt;Both are now shipped. Neither breaks existing templates.&lt;/p&gt;

&lt;p&gt;Let's walk through it.&lt;/p&gt;

&lt;p&gt;BuildKit Support for Image-Based Lambda Functions&lt;br&gt;
The problem&lt;br&gt;
When you run sam build for an image-based Lambda function, SAM uses the docker-py Python SDK under the hood. That SDK talks to the Docker daemon directly, but it doesn't support BuildKit. At all.&lt;/p&gt;

&lt;p&gt;That means every sam build invocation uses the legacy Docker builder. You lose parallelized build stages. You lose efficient layer caching. You lose multi-stage build optimizations. You lose cross-architecture improvements that BuildKit has shipped over the past several years.&lt;/p&gt;

&lt;p&gt;For simple single-stage Dockerfiles, this probably doesn't matter. For anything with multiple stages, private dependencies, or cross-compilation targets, it's a real bottleneck.&lt;/p&gt;

&lt;p&gt;What shipped&lt;br&gt;
SAM CLI v1.156.0 introduced the --use-buildkit flag on sam build. When you pass this flag, SAM bypasses docker-py entirely and shells out to the Docker CLI (or Finch CLI) directly. That gives you access to everything BuildKit offers.&lt;/p&gt;

&lt;p&gt;sam build --use-buildkit&lt;br&gt;
That's it. One flag.&lt;/p&gt;

&lt;p&gt;SAM auto-detects which container runtime you have. It defaults to Docker, falls back to Finch if Docker isn't running, and respects any admin-configured preference. Finch supports BuildKit too, so either runtime works.&lt;/p&gt;

&lt;p&gt;What you get&lt;br&gt;
BuildKit brings real improvements over the legacy builder:&lt;/p&gt;

&lt;p&gt;Parallel stage execution. Multi-stage Dockerfiles build independent stages concurrently instead of sequentially.&lt;br&gt;
Better layer caching. BuildKit tracks dependencies at the file level, not just the layer level. Change one file, rebuild one layer.&lt;br&gt;
Multi-stage build optimizations. BuildKit skips stages that don't contribute to the final output. The legacy builder runs every stage regardless.&lt;br&gt;
Improved cross-architecture support. Building arm64 (Graviton2) images on an x86 machine is more reliable with BuildKit's QEMU integration. Lambda currently supports Graviton2 for arm64 workloads.&lt;br&gt;
Build secrets&lt;br&gt;
SAM CLI v1.159.0 added support for passing BuildKit parameters, including build-time secrets. This lets you pass credentials into a build stage without baking them into a layer. Useful when your Lambda function pulls packages from a private registry during the build.&lt;/p&gt;

&lt;h1&gt;
  
  
  syntax=docker/dockerfile:1
&lt;/h1&gt;

&lt;p&gt;FROM public.ecr.aws/lambda/python:3.12&lt;/p&gt;

&lt;p&gt;RUN --mount=type=secret,id=pip_conf,target=/etc/pip.conf \&lt;br&gt;
    pip install -r requirements.txt&lt;/p&gt;

&lt;p&gt;COPY app.py ${LAMBDA_TASK_ROOT}&lt;br&gt;
CMD ["app.handler"]&lt;br&gt;
You configure secrets through the Metadata section of your function resource, using DockerBuildExtraParams. SAM passes these parameters straight to docker buildx under the hood.&lt;/p&gt;

&lt;p&gt;Resources:&lt;br&gt;
  MyFunction:&lt;br&gt;
    Type: AWS::Serverless::Function&lt;br&gt;
    Properties:&lt;br&gt;
      PackageType: Image&lt;br&gt;
      Architectures: [x86_64]&lt;br&gt;
      Timeout: 10&lt;br&gt;
    Metadata:&lt;br&gt;
      Dockerfile: Dockerfile&lt;br&gt;
      DockerContext: ./src&lt;br&gt;
      DockerTag: latest&lt;br&gt;
      DockerBuildExtraParams:&lt;br&gt;
        - "--secret"&lt;br&gt;
        - "id=pip_conf,src=$HOME/.pip/pip.conf"&lt;br&gt;
The secret mounts into the build stage at the path you specify in the Dockerfile, gets used during pip install, and never appears in the final image layers. Different functions can have different build secrets, since the configuration lives in each function's Metadata block.&lt;/p&gt;

&lt;p&gt;Tradeoffs and limitations&lt;br&gt;
A few things to keep in mind.&lt;/p&gt;

&lt;p&gt;Opt-in only. This doesn't change existing build behavior. If you don't pass --use-buildkit, nothing changes. That's intentional. SAM doesn't break working builds.&lt;/p&gt;

&lt;p&gt;Requires Docker or Finch CLI. BuildKit support works by calling the Docker or Finch CLI directly. If your CI environment only has the Docker daemon (no CLI), this won't work. Most environments have both, but check yours.&lt;/p&gt;

&lt;p&gt;Dockerfile syntax matters. Some BuildKit features, like secret mounts, require the # syntax=docker/dockerfile:1 parser directive at the top of your Dockerfile. Without it, the build falls back to legacy parsing and you get confusing errors.&lt;/p&gt;

&lt;p&gt;Not for ZIP-based functions. This flag only applies to image-based Lambda functions (PackageType: Image). ZIP-based functions don't use Docker at all.&lt;/p&gt;

&lt;p&gt;The rule of thumb I like is this: if your Dockerfile is more than a few lines, or you're building for a different architecture, turn on BuildKit. The caching alone will save you time.&lt;/p&gt;

&lt;p&gt;WebSocket API Support&lt;br&gt;
The problem&lt;br&gt;
Before this release, SAM had no native resource type for WebSocket APIs. You had two options: write raw CloudFormation, or don't use SAM for that part of your stack.&lt;/p&gt;

&lt;p&gt;Here's what a minimal WebSocket API looks like in plain CloudFormation. Three routes ($connect, $disconnect, sendMessage), each backed by a Lambda function:&lt;/p&gt;

&lt;p&gt;Resources:&lt;br&gt;
  WebSocketApi:&lt;br&gt;
    Type: AWS::ApiGatewayV2::Api&lt;br&gt;
    Properties:&lt;br&gt;
      Name: MyWebSocketApi&lt;br&gt;
      ProtocolType: WEBSOCKET&lt;br&gt;
      RouteSelectionExpression: $request.body.action&lt;/p&gt;

&lt;p&gt;ConnectRoute:&lt;br&gt;
    Type: AWS::ApiGatewayV2::Route&lt;br&gt;
    Properties:&lt;br&gt;
      ApiId: !Ref WebSocketApi&lt;br&gt;
      RouteKey: $connect&lt;br&gt;
      Target: !Sub integrations/${ConnectIntegration}&lt;/p&gt;

&lt;p&gt;ConnectIntegration:&lt;br&gt;
    Type: AWS::ApiGatewayV2::Integration&lt;br&gt;
    Properties:&lt;br&gt;
      ApiId: !Ref WebSocketApi&lt;br&gt;
      IntegrationType: AWS_PROXY&lt;br&gt;
      IntegrationUri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${ConnectFunction.Arn}/invocations&lt;/p&gt;

&lt;p&gt;ConnectPermission:&lt;br&gt;
    Type: AWS::Lambda::Permission&lt;br&gt;
    Properties:&lt;br&gt;
      FunctionName: !Ref ConnectFunction&lt;br&gt;
      Action: lambda:InvokeFunction&lt;br&gt;
      Principal: apigateway.amazonaws.com&lt;br&gt;
      SourceArn: !Sub arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${WebSocketApi}/*/$connect&lt;/p&gt;

&lt;p&gt;DisconnectRoute:&lt;br&gt;
    Type: AWS::ApiGatewayV2::Route&lt;br&gt;
    Properties:&lt;br&gt;
      ApiId: !Ref WebSocketApi&lt;br&gt;
      RouteKey: $disconnect&lt;br&gt;
      Target: !Sub integrations/${DisconnectIntegration}&lt;/p&gt;

&lt;p&gt;DisconnectIntegration:&lt;br&gt;
    Type: AWS::ApiGatewayV2::Integration&lt;br&gt;
    Properties:&lt;br&gt;
      ApiId: !Ref WebSocketApi&lt;br&gt;
      IntegrationType: AWS_PROXY&lt;br&gt;
      IntegrationUri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${DisconnectFunction.Arn}/invocations&lt;/p&gt;

&lt;p&gt;DisconnectPermission:&lt;br&gt;
    Type: AWS::Lambda::Permission&lt;br&gt;
    Properties:&lt;br&gt;
      FunctionName: !Ref DisconnectFunction&lt;br&gt;
      Action: lambda:InvokeFunction&lt;br&gt;
      Principal: apigateway.amazonaws.com&lt;br&gt;
      SourceArn: !Sub arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${WebSocketApi}/*/$disconnect&lt;/p&gt;

&lt;p&gt;SendMessageRoute:&lt;br&gt;
    Type: AWS::ApiGatewayV2::Route&lt;br&gt;
    Properties:&lt;br&gt;
      ApiId: !Ref WebSocketApi&lt;br&gt;
      RouteKey: sendMessage&lt;br&gt;
      Target: !Sub integrations/${SendMessageIntegration}&lt;/p&gt;

&lt;p&gt;SendMessageIntegration:&lt;br&gt;
    Type: AWS::ApiGatewayV2::Integration&lt;br&gt;
    Properties:&lt;br&gt;
      ApiId: !Ref WebSocketApi&lt;br&gt;
      IntegrationType: AWS_PROXY&lt;br&gt;
      IntegrationUri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${SendMessageFunction.Arn}/invocations&lt;/p&gt;

&lt;p&gt;SendMessagePermission:&lt;br&gt;
    Type: AWS::Lambda::Permission&lt;br&gt;
    Properties:&lt;br&gt;
      FunctionName: !Ref SendMessageFunction&lt;br&gt;
      Action: lambda:InvokeFunction&lt;br&gt;
      Principal: apigateway.amazonaws.com&lt;br&gt;
      SourceArn: !Sub arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${WebSocketApi}/*/sendMessage&lt;/p&gt;

&lt;p&gt;Deployment:&lt;br&gt;
    Type: AWS::ApiGatewayV2::Deployment&lt;br&gt;
    DependsOn:&lt;br&gt;
      - ConnectRoute&lt;br&gt;
      - DisconnectRoute&lt;br&gt;
      - SendMessageRoute&lt;br&gt;
    Properties:&lt;br&gt;
      ApiId: !Ref WebSocketApi&lt;/p&gt;

&lt;p&gt;Stage:&lt;br&gt;
    Type: AWS::ApiGatewayV2::Stage&lt;br&gt;
    Properties:&lt;br&gt;
      ApiId: !Ref WebSocketApi&lt;br&gt;
      StageName: prod&lt;br&gt;
      DeploymentId: !Ref Deployment&lt;br&gt;
That's twelve resources for three routes. Around 90 lines of YAML. And I left out the Lambda function definitions.&lt;/p&gt;

&lt;p&gt;Notice the DependsOn on the Deployment resource. If you forget that, CloudFormation tries to create the deployment before the routes exist, and the stack fails. You have to manage that dependency graph yourself.&lt;/p&gt;

&lt;p&gt;That's a lot of ceremony for "connect a WebSocket to some Lambda functions."&lt;/p&gt;

&lt;p&gt;What shipped&lt;br&gt;
The new AWS::Serverless::WebSocketApi resource type collapses all of that into this:&lt;/p&gt;

&lt;p&gt;Resources:&lt;br&gt;
  MyWebSocketApi:&lt;br&gt;
    Type: AWS::Serverless::WebSocketApi&lt;br&gt;
    Properties:&lt;br&gt;
      RouteSelectionExpression: $request.body.action&lt;br&gt;
      StageName: prod&lt;br&gt;
      Routes:&lt;br&gt;
        $connect:&lt;br&gt;
          FunctionArn: !GetAtt ConnectFunction.Arn&lt;br&gt;
        $disconnect:&lt;br&gt;
          FunctionArn: !GetAtt DisconnectFunction.Arn&lt;br&gt;
        sendMessage:&lt;br&gt;
          FunctionArn: !GetAtt SendMessageFunction.Arn&lt;br&gt;
Compare that to the CloudFormation version. Twelve resources become one. Ninety lines become twelve. The routes, integrations, Lambda permissions, deployment, stage, and resource ordering are all handled by SAM's transform.&lt;/p&gt;

&lt;p&gt;You define the routes. SAM generates the rest.&lt;/p&gt;

&lt;p&gt;What SAM handles automatically&lt;br&gt;
For each route you declare, SAM creates:&lt;/p&gt;

&lt;p&gt;The AWS::ApiGatewayV2::Route resource&lt;br&gt;
The AWS::ApiGatewayV2::Integration wiring the route to your Lambda function&lt;br&gt;
The AWS::Lambda::Permission granting API Gateway invoke access&lt;br&gt;
If you add a Lambda authorizer, the authorizer permission too&lt;br&gt;
SAM also creates the deployment and stage resources, with the correct dependency ordering. No DependsOn blocks to manage.&lt;/p&gt;

&lt;p&gt;Full feature parity&lt;br&gt;
This isn't a simplified subset. The new resource type supports everything API Gateway V2 WebSocket offers:&lt;/p&gt;

&lt;p&gt;Auth: IAM authorization and Lambda authorizers, per-route or API-wide&lt;br&gt;
Custom domains: Map your WebSocket API to your own domain&lt;br&gt;
Route settings: Configure throttling and logging per route via RouteSettings&lt;br&gt;
Models: Attach request/response models for validation&lt;br&gt;
Stage variables: Pass configuration to your integration through stage variables&lt;br&gt;
Globals: Share configuration across multiple WebSocket APIs using the SAM Globals section&lt;br&gt;
Use cases&lt;br&gt;
WebSocket APIs are the right tool when you need a persistent, bidirectional connection. Common patterns:&lt;/p&gt;

&lt;p&gt;Chat applications. Users send and receive messages in real time.&lt;br&gt;
Live dashboards. Push metric updates to connected browsers without polling.&lt;br&gt;
AI/LLM streaming. Stream token-by-token responses from a model back to the client. This one is increasingly common.&lt;br&gt;
IoT command channels. Send commands to devices and receive status updates on the same connection.&lt;br&gt;
If your current approach is a REST API that the client polls every few seconds, a WebSocket API will give you lower latency and lower cost. Fewer requests, fewer Lambda invocations, faster updates.&lt;/p&gt;

&lt;p&gt;Tradeoffs and limitations&lt;br&gt;
No local emulation. SAM CLI doesn't support sam local start-api for WebSocket APIs. You can test individual Lambda handlers with sam local invoke, but end-to-end local WebSocket testing isn't available yet. Deploy to a dev stage for integration testing.&lt;/p&gt;

&lt;p&gt;No sam local start-websocket. Related to the above, there's no dedicated local command for WebSocket APIs like there is for HTTP APIs with sam local start-api.&lt;/p&gt;

&lt;p&gt;For the Agents&lt;br&gt;
If you're using an AI coding agent to build SAM applications, both of these features work out of the box with agent-driven workflows. Your agent can scaffold a WebSocket API or add BuildKit to an existing image-based function without any special setup.&lt;/p&gt;

&lt;p&gt;For Kiro, there's an official AWS SAM Power that gives your agent SAM-aware tooling. Install it and your agent gets access to sam_init, sam_build, sam_deploy, sam_logs, and sam_local_invoke as callable tools, plus opinionated project structure guidance.&lt;/p&gt;

&lt;p&gt;Here's what a Kiro-assisted WebSocket API scaffold looks like in practice:&lt;/p&gt;

&lt;p&gt;You: Create a WebSocket API with connect, disconnect, and sendMessage routes.&lt;br&gt;
     Use the SAM Power.&lt;/p&gt;

&lt;p&gt;Kiro: [runs sam_init] → creates project structure&lt;br&gt;
      [updates template.yaml] → adds AWS::Serverless::WebSocketApi&lt;br&gt;
      [creates Lambda handlers] → connect.py, disconnect.py, send_message.py&lt;br&gt;
      [runs sam_build] → builds the project&lt;br&gt;
      [runs sam_local_invoke] → tests the connect handler locally&lt;br&gt;
The SAM Power also enforces good project structure: separate Lambda handlers in infrastructure/lambda/, proper CodeUri paths, and .aws-sam in your .gitignore. It gets you from idea to deployed WebSocket API without hand-writing boilerplate.&lt;/p&gt;

&lt;p&gt;You can install the SAM Power from the Kiro Powers marketplace or add it directly to your project's .kiro/powers/ directory.&lt;/p&gt;

&lt;p&gt;Getting Started&lt;br&gt;
Upgrade SAM CLI to get both features:&lt;/p&gt;

&lt;p&gt;sam --version&lt;/p&gt;

&lt;h1&gt;
  
  
  Need v1.156.0 or later for BuildKit, latest for WebSocket APIs
&lt;/h1&gt;

&lt;h1&gt;
  
  
  Upgrade via pip
&lt;/h1&gt;

&lt;p&gt;pip install --upgrade aws-sam-cli&lt;/p&gt;

&lt;h1&gt;
  
  
  Or via Homebrew
&lt;/h1&gt;

&lt;p&gt;brew upgrade aws-sam-cli&lt;br&gt;
For BuildKit, add --use-buildkit to your sam build command. No template changes needed.&lt;/p&gt;

&lt;p&gt;For WebSocket APIs, replace your CloudFormation resources with the new AWS::Serverless::WebSocketApi type. If you're starting fresh, the SAM template above is a working starting point.&lt;/p&gt;

&lt;p&gt;BuildKit works with sam local for local testing. WebSocket APIs currently support sam deploy for deployment and sam local invoke for testing individual handlers, but full local WebSocket emulation isn't available yet.&lt;/p&gt;

&lt;p&gt;Two Features, Zero Breaking Changes&lt;br&gt;
These two features fill gaps that have been open for a while. BuildKit support means sam build finally uses the same build engine as the rest of the container ecosystem. WebSocket API support means you can define a real-time API in SAM the same way you define a REST API. A few lines instead of a hundred.&lt;/p&gt;

&lt;p&gt;Neither feature changes existing behavior. Both are additive. Upgrade, try them, and keep building.&lt;/p&gt;

</description>
      <category>awssam</category>
      <category>apigateway</category>
      <category>websockets</category>
      <category>docker</category>
    </item>
    <item>
      <title>Cold Starts Are Dead</title>
      <dc:creator>Eric D Johnson</dc:creator>
      <pubDate>Wed, 29 Apr 2026 03:09:17 +0000</pubDate>
      <link>https://forem.com/aws/cold-starts-are-dead-5fod</link>
      <guid>https://forem.com/aws/cold-starts-are-dead-5fod</guid>
      <description>&lt;p&gt;It never fails. Every time I talk about serverless, someone pushes back with the cold start argument. I still see it in forums, in blog comments, in architecture review meetings. "Sure, but what about cold starts?"&lt;/p&gt;

&lt;p&gt;I get it. Five or six years ago, that was a legitimate concern.&lt;/p&gt;

&lt;p&gt;But it's 2026. The data tells a different story. And if you're still making decisions based on the cold start argument, you're arguing against a version of Lambda that hasn't existed in years.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Long Are Lambda Cold Starts in 2026?
&lt;/h2&gt;

&lt;p&gt;Let's start with what cold starts actually look like today. These numbers come from &lt;a href="https://tasrieit.com/blog/aws-lambda-best-practices-production-2026" rel="noopener noreferrer"&gt;production workloads observed in the wild&lt;/a&gt;, not synthetic hello-world tests. Your mileage will vary by package size, initialization code, and memory configuration, but the ranges are representative of what teams are seeing in 2026.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Runtime&lt;/th&gt;
&lt;th&gt;P50&lt;/th&gt;
&lt;th&gt;P99&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Python 3.13&lt;/td&gt;
&lt;td&gt;200-400ms&lt;/td&gt;
&lt;td&gt;800ms-1.2s&lt;/td&gt;
&lt;td&gt;Fastest scripting runtime&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Node.js 22&lt;/td&gt;
&lt;td&gt;200-350ms&lt;/td&gt;
&lt;td&gt;600ms-1s&lt;/td&gt;
&lt;td&gt;Solid general choice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Go&lt;/td&gt;
&lt;td&gt;50-100ms&lt;/td&gt;
&lt;td&gt;150-250ms&lt;/td&gt;
&lt;td&gt;Near-zero&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rust&lt;/td&gt;
&lt;td&gt;50-80ms&lt;/td&gt;
&lt;td&gt;100-200ms&lt;/td&gt;
&lt;td&gt;Fastest overall&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Java 21 (no SnapStart)&lt;/td&gt;
&lt;td&gt;2-5s&lt;/td&gt;
&lt;td&gt;6-10s&lt;/td&gt;
&lt;td&gt;Still slow without SnapStart&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Java 21 + SnapStart&lt;/td&gt;
&lt;td&gt;90-140ms&lt;/td&gt;
&lt;td&gt;200-400ms&lt;/td&gt;
&lt;td&gt;Dramatically better&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Running on arm64 (Graviton)? Knock another &lt;a href="https://tasrieit.com/blog/aws-lambda-best-practices-production-2026" rel="noopener noreferrer"&gt;15-40% off those numbers&lt;/a&gt; across the board.&lt;/p&gt;

&lt;p&gt;Rust cold starts have been &lt;a href="https://www.nandann.com/blog/rust-aws-lambda-production-guide" rel="noopener noreferrer"&gt;measured as low as 16ms&lt;/a&gt; on arm64. Sixteen milliseconds, which is less a cold start problem and more a rounding error.&lt;/p&gt;

&lt;p&gt;The scripting runtimes, Python and Node, land in the 200-400ms range at P50. For context, that's less than the time it takes your browser to render a page after receiving the HTML.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does VPC Still Cause Lambda Cold Starts?
&lt;/h2&gt;

&lt;p&gt;This one still comes up in conversations, and it frustrates me because it was solved in 2019.&lt;/p&gt;

&lt;p&gt;The old problem: when a Lambda function needed VPC connectivity, AWS had to create an Elastic Network Interface (ENI) on the fly. That meant 10-15 seconds of additional cold start latency on top of everything else. VPC-connected Lambda was genuinely painful.&lt;/p&gt;

&lt;p&gt;AWS fixed this when Lambda &lt;a href="https://www.infoq.com/news/2019/09/aws-lambda-vpc/" rel="noopener noreferrer"&gt;migrated to Firecracker microVMs in 2019&lt;/a&gt;, dropping cold start overhead from over ten seconds to under a second. Werner Vogels recently wrote about &lt;a href="https://www.allthingsdistributed.com/2026/04/the-invisible-engineering-behind-lambdas-network.html" rel="noopener noreferrer"&gt;the invisible engineering behind Lambda's network&lt;/a&gt;. The team used eBPF to rewrite Geneve tunnel headers, taking tunnel latency from 150 milliseconds to 200 &lt;em&gt;microseconds&lt;/em&gt;. The VPC cold start penalty now approaches zero for most workloads.&lt;/p&gt;

&lt;p&gt;That was seven years ago. If someone tells you Lambda cold starts are bad because of VPC, they're working from outdated information.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Much Does SnapStart Reduce Cold Starts?
&lt;/h2&gt;

&lt;p&gt;Java was the poster child for the cold start argument. And honestly, it earned that reputation. A Spring Boot app on Lambda could take &lt;a href="https://kindatechnical.com/aws-lambda/lambda-snapstart-for-java-snapshot-based-initialization-and-performance-gains.html" rel="noopener noreferrer"&gt;3-10 seconds to cold start&lt;/a&gt;. That's painful no matter how you frame it.&lt;/p&gt;

&lt;p&gt;SnapStart changed the math. Here's the timeline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;November 2022&lt;/strong&gt;: SnapStart GA for Java at re:Invent&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;November 2024&lt;/strong&gt;: Python 3.12+ and .NET 8+ GA&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2025&lt;/strong&gt;: Expanded to additional regions, arm64 support&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/blogs/compute/under-the-hood-how-aws-lambda-snapstart-optimizes-function-startup-latency/?trk=f7d9a1d9-5cbf-4d49-96aa-491d20cae74f&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;How it works&lt;/a&gt;: Lambda takes a snapshot of the initialized Firecracker microVM after your INIT code runs. That snapshot gets cached across three tiers: L1 on the worker, L2 in the placement group, S3 at the region level. On cold start, Lambda restores from the snapshot instead of re-running your initialization code.&lt;/p&gt;

&lt;p&gt;The benchmarks are real:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Python (Flask, LangChain, Pandas)&lt;/strong&gt;: &lt;a href="https://aws.amazon.com/blogs/aws/aws-lambda-snapstart-for-python-and-net-functions-is-now-generally-available/?trk=f7d9a1d9-5cbf-4d49-96aa-491d20cae74f&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;several seconds → sub-second&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Java Spring Boot&lt;/strong&gt;: &lt;a href="https://kindatechnical.com/aws-lambda/lambda-snapstart-for-java-snapshot-based-initialization-and-performance-gains.html" rel="noopener noreferrer"&gt;5.8s → 180ms&lt;/a&gt; (97% reduction)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;.NET&lt;/strong&gt;: &lt;a href="https://codewithmukesh.com/blog/lambda-snapstart-dotnet/" rel="noopener noreferrer"&gt;58-94% cold start reduction&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;SnapStart has continued to evolve. &lt;a href="https://aws.amazon.com/blogs/aws/aws-lambda-snapstart-for-python-and-net-functions-is-now-generally-available/?trk=f7d9a1d9-5cbf-4d49-96aa-491d20cae74f&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Python and .NET support&lt;/a&gt; went GA in late 2024, with additional regions and arm64 support following in 2025.&lt;/p&gt;

&lt;h2&gt;
  
  
  Did the Lambda INIT Billing Change Increase Costs?
&lt;/h2&gt;

&lt;p&gt;In August 2025, AWS &lt;a href="https://aws.amazon.com/blogs/compute/aws-lambda-standardizes-billing-for-init-phase/?trk=f7d9a1d9-5cbf-4d49-96aa-491d20cae74f&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;standardized billing for the Lambda INIT phase&lt;/a&gt;. Functions packaged as ZIP files with managed runtimes now get billed for the INIT phase, which was previously free. This triggered some alarming blog posts.&lt;/p&gt;

&lt;p&gt;The headline claim: a "22x cost increase." Let's look at the math.&lt;/p&gt;

&lt;p&gt;That 22x number requires a perfect storm of worst-case assumptions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;100% cold start rate (every invocation is a cold start)&lt;/li&gt;
&lt;li&gt;2-second Java INIT duration&lt;/li&gt;
&lt;li&gt;512MB memory configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's the problem with that scenario: &lt;strong&gt;&lt;a href="https://aws.amazon.com/blogs/compute/aws-lambda-standardizes-billing-for-init-phase/?trk=f7d9a1d9-5cbf-4d49-96aa-491d20cae74f&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;AWS's own production analysis&lt;/a&gt; shows cold starts occur in less than 1% of invocations.&lt;/strong&gt; Not 100%. Less than 1%.&lt;/p&gt;

&lt;p&gt;AWS's own assessment: "most users will see minimal impact on their overall Lambda bill from this change, as the INIT phase typically occurs for a very small fraction of function invocations." The actual impact depends on your cold start ratio and INIT duration relative to handler duration. Use the CloudWatch query below to check your own numbers.&lt;/p&gt;

&lt;p&gt;Don't take my word for it. AWS published a CloudWatch Logs Insights query so you can calculate your exact impact:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;filter&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;"REPORT"&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;stats&lt;/span&gt;
    &lt;span class="k"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;memorySize&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1000000&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;billedDuration&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;BilledGBs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;memorySize&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1000000&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;duration&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;initDuration&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;billedDuration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;UnbilledInitGBs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;UnbilledInitGBs&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UnbilledInitGBs&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;BilledGBs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;UnbilledInitRatio&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run that against your own functions. If your &lt;code&gt;UnbilledInitRatio&lt;/code&gt; is anywhere near the number that would produce a 22x increase, you have a bigger problem than billing changes. You have an architecture problem.&lt;/p&gt;

&lt;p&gt;With SnapStart enabled, INIT durations drop to sub-second, which shrinks that ratio even further.&lt;/p&gt;

&lt;h2&gt;
  
  
  I Decided to See for Myself
&lt;/h2&gt;

&lt;p&gt;I wasn't satisfied citing someone else's numbers for a post called "Cold Starts Are Dead." So I built a benchmarker. Thirteen Lambda functions across six runtimes, both arm64 and x86_64, 50 cold start invocations each, 512MB memory, us-west-2. Minimal hello-world handlers, no frameworks, no dependencies beyond the runtime SDK. This measures the platform floor, not application init time.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Runtime&lt;/th&gt;
&lt;th&gt;Arch&lt;/th&gt;
&lt;th&gt;P50 (ms)&lt;/th&gt;
&lt;th&gt;P99 (ms)&lt;/th&gt;
&lt;th&gt;Blog Claim&lt;/th&gt;
&lt;th&gt;Verdict&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Rust&lt;/td&gt;
&lt;td&gt;arm64&lt;/td&gt;
&lt;td&gt;14.1&lt;/td&gt;
&lt;td&gt;31.9&lt;/td&gt;
&lt;td&gt;50-80ms&lt;/td&gt;
&lt;td&gt;⚡ Faster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rust&lt;/td&gt;
&lt;td&gt;x86_64&lt;/td&gt;
&lt;td&gt;17.0&lt;/td&gt;
&lt;td&gt;29.3&lt;/td&gt;
&lt;td&gt;50-80ms&lt;/td&gt;
&lt;td&gt;⚡ Faster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Go&lt;/td&gt;
&lt;td&gt;arm64&lt;/td&gt;
&lt;td&gt;45.0&lt;/td&gt;
&lt;td&gt;61.2&lt;/td&gt;
&lt;td&gt;50-100ms&lt;/td&gt;
&lt;td&gt;⚡ Faster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Go&lt;/td&gt;
&lt;td&gt;x86_64&lt;/td&gt;
&lt;td&gt;59.8&lt;/td&gt;
&lt;td&gt;94.9&lt;/td&gt;
&lt;td&gt;50-100ms&lt;/td&gt;
&lt;td&gt;✅ Verified&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python 3.13&lt;/td&gt;
&lt;td&gt;arm64&lt;/td&gt;
&lt;td&gt;88.3&lt;/td&gt;
&lt;td&gt;147.1&lt;/td&gt;
&lt;td&gt;200-400ms&lt;/td&gt;
&lt;td&gt;⚡ Faster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python 3.13&lt;/td&gt;
&lt;td&gt;x86_64&lt;/td&gt;
&lt;td&gt;106.2&lt;/td&gt;
&lt;td&gt;142.3&lt;/td&gt;
&lt;td&gt;200-400ms&lt;/td&gt;
&lt;td&gt;⚡ Faster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Node.js 22&lt;/td&gt;
&lt;td&gt;arm64&lt;/td&gt;
&lt;td&gt;121.5&lt;/td&gt;
&lt;td&gt;168.5&lt;/td&gt;
&lt;td&gt;200-350ms&lt;/td&gt;
&lt;td&gt;⚡ Faster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Node.js 22&lt;/td&gt;
&lt;td&gt;x86_64&lt;/td&gt;
&lt;td&gt;155.0&lt;/td&gt;
&lt;td&gt;231.3&lt;/td&gt;
&lt;td&gt;200-350ms&lt;/td&gt;
&lt;td&gt;⚡ Faster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Java 21&lt;/td&gt;
&lt;td&gt;arm64&lt;/td&gt;
&lt;td&gt;365.3&lt;/td&gt;
&lt;td&gt;539.2&lt;/td&gt;
&lt;td&gt;2-5s&lt;/td&gt;
&lt;td&gt;⚡ Faster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Java 21&lt;/td&gt;
&lt;td&gt;x86_64&lt;/td&gt;
&lt;td&gt;443.8&lt;/td&gt;
&lt;td&gt;573.5&lt;/td&gt;
&lt;td&gt;2-5s&lt;/td&gt;
&lt;td&gt;⚡ Faster&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every runtime came in at or below the production ranges I cited earlier. That's expected. Those production numbers include application dependencies, framework initialization, and SDK client setup. My minimal handlers isolate just the platform overhead. Think of these as the floor: your cold starts will be at least this fast, plus whatever your initialization code adds.&lt;/p&gt;

&lt;p&gt;The arm64 advantage verified cleanly too:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Runtime&lt;/th&gt;
&lt;th&gt;arm64 P50&lt;/th&gt;
&lt;th&gt;x86_64 P50&lt;/th&gt;
&lt;th&gt;Improvement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Rust&lt;/td&gt;
&lt;td&gt;14.1ms&lt;/td&gt;
&lt;td&gt;17.0ms&lt;/td&gt;
&lt;td&gt;17% faster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Go&lt;/td&gt;
&lt;td&gt;45.0ms&lt;/td&gt;
&lt;td&gt;59.8ms&lt;/td&gt;
&lt;td&gt;25% faster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;88.3ms&lt;/td&gt;
&lt;td&gt;106.2ms&lt;/td&gt;
&lt;td&gt;17% faster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Node.js&lt;/td&gt;
&lt;td&gt;121.5ms&lt;/td&gt;
&lt;td&gt;155.0ms&lt;/td&gt;
&lt;td&gt;22% faster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Java&lt;/td&gt;
&lt;td&gt;365.3ms&lt;/td&gt;
&lt;td&gt;443.8ms&lt;/td&gt;
&lt;td&gt;18% faster&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;17-25% faster across every runtime. If you're still deploying on x86_64 in 2026, you're leaving performance on the table for no reason.&lt;/p&gt;

&lt;p&gt;I also tested VPC vs non-VPC with the Python function. The VPC-connected function was 1.4ms &lt;em&gt;faster&lt;/em&gt; at P50, within noise.&lt;/p&gt;

&lt;p&gt;One honest caveat: SnapStart on my minimal Java handler showed a ~670ms restore duration. That's because there's almost nothing to snapshot. The restore mechanism's own overhead dominates. For a Spring Boot app where SnapStart eliminates 3-5 seconds of framework init, you'd see the dramatic improvement the benchmarks above describe. SnapStart's value scales with how much init work your app does.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/singledigit/lambda-benchmark" rel="noopener noreferrer"&gt;full benchmarker is open source on GitHub&lt;/a&gt;. You can run it yourself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/singledigit/lambda-benchmark.git
&lt;span class="nb"&gt;cd &lt;/span&gt;lambda-benchmark
sam build &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; sam deploy &lt;span class="nt"&gt;--guided&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;orchestrator
python benchmark.py &lt;span class="nt"&gt;--stack-name&lt;/span&gt; &amp;lt;stack&amp;gt; &lt;span class="nt"&gt;--iterations&lt;/span&gt; 50
python generate_report.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  When Do Lambda Cold Starts Still Matter?
&lt;/h2&gt;

&lt;p&gt;Here's where cold starts still matter.&lt;/p&gt;

&lt;p&gt;But first, is 200ms even slow? For a website, a chat app, an API powering a mobile experience, an AI agent? You won't notice it. A typical page load involves DNS resolution, TLS handshake, and content rendering that add up to far more than 200ms. If you're building AI agents, the LLM call alone takes 2-10 seconds, so 200ms of cold start is a rounding error on that. It's the first request after a quiet period, and it happens in less time than a blink.&lt;/p&gt;

&lt;p&gt;The cases where cold starts actually matter are narrow:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sub-10ms latency SLAs.&lt;/strong&gt; If you're building for high-frequency trading or real-time bidding, 200ms of cold start latency is unacceptable. At that point, you're probably looking at containers on ECS or EKS where you control the warm pool directly. Provisioned Concurrency is an option too, but it has its own cost tradeoffs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Very spiky, unpredictable traffic with strict P99 requirements.&lt;/strong&gt; If your traffic pattern goes from zero to thousands of concurrent invocations with no ramp-up, and you have hard P99 latency SLAs, cold starts will hit that tail. You have options: containers with pre-scaled task counts, Provisioned Concurrency, or accepting slightly higher P99s during the initial burst. The right answer depends on your cost tolerance and how strict "strict" actually is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Java without SnapStart.&lt;/strong&gt; Just... use SnapStart. If you're running Java on Lambda without SnapStart enabled, you're choosing to have a cold start problem. The fix is a configuration change.&lt;/p&gt;

&lt;p&gt;The honest framing: the question isn't "do cold starts exist?" They do. The question is "do they matter for your workload?" For the vast majority of workloads, the answer is no.&lt;/p&gt;

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

&lt;p&gt;VPC cold starts were fixed in 2019, and SnapStart took Java from 5 seconds to 180 milliseconds. Python and Node sit under 200ms on arm64, and AWS's own data says cold starts happen in less than 1% of invocations. I built a benchmarker and tested it myself. The production claims are conservative, and the numbers are better than advertised.&lt;/p&gt;

&lt;p&gt;The cold start argument had its day. That day was 2018. If you're still leading with it, I get it, it used to be real. But the data has moved on, and it's time we did too.&lt;/p&gt;

&lt;h2&gt;
  
  
  For the Agents
&lt;/h2&gt;

&lt;p&gt;If you're using an AI coding assistant or agent, I've put together a companion skill file for this post. It includes the 2026 cold start benchmarks, SnapStart configurations, and the CloudWatch query for INIT billing analysis. Everything your agent needs to give you accurate cold start guidance instead of outdated advice.&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>aws</category>
      <category>lambda</category>
      <category>coldstarts</category>
    </item>
    <item>
      <title>Is this code deterministic?</title>
      <dc:creator>Eric D Johnson</dc:creator>
      <pubDate>Fri, 16 Jan 2026 18:20:30 +0000</pubDate>
      <link>https://forem.com/aws/is-this-code-deterministic-29l9</link>
      <guid>https://forem.com/aws/is-this-code-deterministic-29l9</guid>
      <description>&lt;p&gt;I recently posted a small code snippet in a LinkedIn poll and asked what sounded like a simple question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is this code deterministic?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Those are usually the dangerous questions.&lt;/p&gt;

&lt;p&gt;I asked on purpose. I’ve been spending time talking with folks much smarter than me, reading docs, and honestly leaning on code assistants to sanity-check my thinking as I go. Durable execution has a way of surfacing edge cases you don’t normally think about, and I wanted to learn in public—right alongside everyone else.&lt;/p&gt;

&lt;p&gt;The discussion that followed (in &lt;a href="https://www.linkedin.com/feed/update/urn:li:activity:7416921277363302400/" rel="noopener noreferrer"&gt;the original post&lt;/a&gt;) was excellent. It also showed how easy it is to mix together concepts like determinism, replay, retries, and idempotency. This post is my attempt to slow things down and separate those ideas, using the original example and AWS’s guidance on &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/durable-best-practices.html" rel="noopener noreferrer"&gt;deterministic code in AWS Lambda durable functions&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here’s the code that started it all:&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;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="kr"&gt;any&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;orders&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;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;priority&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;priority&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="p"&gt;[];&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;order&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;orders&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;result&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="s2"&gt;`process-&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;id&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="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;processOrder&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="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;push&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="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;processed&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="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;timestamp&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="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;Most people voted &lt;strong&gt;“No, non-deterministic.”&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
That’s the correct answer—but not always for the reasons people first reach for.&lt;/p&gt;

&lt;p&gt;Let’s walk through it.&lt;/p&gt;


&lt;h2&gt;
  
  
  Problem 1: Equal-priority ordering is under-specified
&lt;/h2&gt;
&lt;h3&gt;
  
  
  What’s happening
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;priority&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Modern JavaScript engines (ES2019+) guarantee that &lt;code&gt;Array.prototype.sort()&lt;/code&gt; is stable. If two orders have the same priority, their relative order is preserved.&lt;/p&gt;

&lt;p&gt;So no, JavaScript isn’t secretly reordering your data.&lt;/p&gt;
&lt;h3&gt;
  
  
  Why it still matters (and why this one is subtle)
&lt;/h3&gt;

&lt;p&gt;I’ll be honest: this one felt nit-picky to me at first. If the input is the same and the sort is stable, it &lt;em&gt;feels&lt;/em&gt; like everything should be fine.&lt;/p&gt;

&lt;p&gt;The important realization is this: a stable sort preserves whatever order the input already had—but it doesn’t explain &lt;em&gt;why&lt;/em&gt; that order exists.&lt;/p&gt;

&lt;p&gt;In this code, the implicit rule becomes:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“If priorities are equal, keep whatever order the input arrived in.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If that order is intentional and guaranteed, great. Nothing wrong here.&lt;br&gt;&lt;br&gt;
But if it’s incidental—maybe merged upstream, aggregated from multiple sources, or simply not meant to be meaningful—then the workflow’s step ordering now depends on an accident of the input.&lt;/p&gt;

&lt;p&gt;Nothing is broken. But you may have just encoded behavior you didn’t mean to encode.&lt;/p&gt;

&lt;p&gt;One more thing worth saying out loud: if the order in which steps are created doesn’t matter, you may not need to sort at all. Sorting only makes sense if you’re enforcing a real business rule like “highest priority goes first.”&lt;/p&gt;
&lt;h3&gt;
  
  
  Why not just wrap the ordering in a step?
&lt;/h3&gt;

&lt;p&gt;This is a very common reaction—and a reasonable one.&lt;/p&gt;

&lt;p&gt;Yes, you &lt;em&gt;could&lt;/em&gt; wrap the sort in a step and checkpoint it. That would make the ordering fully durable and replay-stable.&lt;/p&gt;

&lt;p&gt;But steps are not free.&lt;/p&gt;

&lt;p&gt;They add latency. They cost money. They count toward operation limits. And they exist primarily to protect work that is slow, expensive, or has side effects.&lt;/p&gt;

&lt;p&gt;Pure, fast, in-memory logic like sorting is already replayable. Re-running a sort of 10 items during replay is usually far cheaper than checkpointing it. Even with larger lists, the trade-off depends on size, cost, and intent.&lt;/p&gt;

&lt;p&gt;The rule of thumb I like is this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If the logic is pure, fast, and deterministic, don’t rush to wrap it.&lt;br&gt;&lt;br&gt;
If you &lt;em&gt;can’t&lt;/em&gt; make it deterministic, or replaying it is expensive, that’s when a step makes sense.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Fix
&lt;/h3&gt;

&lt;p&gt;If ordering matters, make it explicit and deterministic, without mutating the input:&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;orders&lt;/span&gt; &lt;span class="o"&gt;=&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;orders&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;priority&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;priority&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;a&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="nf"&gt;localeCompare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&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="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Problem 2: &lt;code&gt;Date.now()&lt;/code&gt; is non-deterministic
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What’s happening
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;timestamp&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This value is computed at runtime, so every execution produces a different number.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why it matters
&lt;/h3&gt;

&lt;p&gt;In this handler, the timestamp is just part of the returned response. It doesn’t affect control flow or step scheduling, so it’s harmless &lt;em&gt;today&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;But time-based APIs are explicitly called out in the durable execution docs as a common source of non-determinism. If this value later gets stored as workflow state, passed into a step, or used in a conditional, replay behavior can change in ways that are very hard to reason about.&lt;/p&gt;

&lt;p&gt;This is less “this is wrong” and more “this is easy to trip over later.”&lt;/p&gt;

&lt;h3&gt;
  
  
  Fix
&lt;/h3&gt;

&lt;p&gt;If the timestamp actually matters, capture it once inside a step so it replays consistently:&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;timestamp&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="s2"&gt;timestamp&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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Problem 3: Side effects hidden inside a single step
&lt;/h2&gt;

&lt;p&gt;Ben Kehoe correctly points out a subtle but important issue in this code.&lt;/p&gt;

&lt;h3&gt;
  
  
  What’s happening
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&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="s2"&gt;`process-&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;id&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="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;processOrder&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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A durable step can fail and be retried. Once a step completes, it won’t be re-run on replay—but retries &lt;em&gt;can&lt;/em&gt; re-execute the step body.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;processOrder&lt;/code&gt; performs multiple side effects, a failure partway through can cause those side effects to run again.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why it matters
&lt;/h3&gt;

&lt;p&gt;This is not a determinism problem.&lt;br&gt;&lt;br&gt;
It’s not a replay problem either.&lt;/p&gt;

&lt;p&gt;This is a &lt;strong&gt;retry safety&lt;/strong&gt; problem.&lt;/p&gt;

&lt;p&gt;If a step body can’t safely run more than once, retries can produce duplicate effects unless everything inside the step is idempotent.&lt;/p&gt;
&lt;h3&gt;
  
  
  Fix
&lt;/h3&gt;

&lt;p&gt;Be intentional about retry boundaries and align steps with retry-safe work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problematic version:&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;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="s2"&gt;`process-&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;id&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="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;chargeCard&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="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;writeAuditRecord&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="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sendConfirmation&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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If this fails after &lt;code&gt;chargeCard&lt;/code&gt;, a retry may re-run everything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Safer version:&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;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="s2"&gt;`charge-&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;id&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="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;chargeCard&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;idempotencyKey&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;id&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;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="s2"&gt;`audit-&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;id&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="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;writeAuditRecord&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="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="s2"&gt;`notify-&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;id&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="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;sendConfirmation&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;idempotencyKey&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;id&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This doesn’t magically make things idempotent. It just limits the blast radius when retries happen.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step semantics and retry intent
&lt;/h3&gt;

&lt;p&gt;AWS Lambda durable functions also give you control over &lt;em&gt;how&lt;/em&gt; steps retry.&lt;/p&gt;

&lt;p&gt;By default, steps use &lt;code&gt;AtLeastOncePerRetry&lt;/code&gt; semantics. If a step fails or the Lambda is interrupted, the runtime may re-execute the step body. In this mode, the retry count acts as a &lt;strong&gt;lower bound&lt;/strong&gt; on executions.&lt;/p&gt;

&lt;p&gt;If you have a step that must never run more than once, you can use &lt;code&gt;StepSemantics.AtMostOncePerRetry&lt;/code&gt; with zero retries. In that case, a failure surfaces as an error instead of re-running the step.&lt;/p&gt;

&lt;p&gt;Put simply:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AtLeastOncePerRetry&lt;/strong&gt; → max attempts is a lower bound
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AtMostOncePerRetry&lt;/strong&gt; → max attempts is an upper bound
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Neither is “better.” They just encode different assumptions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Putting it all together
&lt;/h2&gt;

&lt;p&gt;Once you make ordering explicit, keep non-deterministic values under control, and think carefully about retry boundaries, the handler becomes much easier to reason about.&lt;/p&gt;

&lt;p&gt;Here are two durable-safe ways to structure it, depending on how independent your work items are and how much concurrency you want.&lt;/p&gt;




&lt;h2&gt;
  
  
  Durable-safe handler (step by step)
&lt;/h2&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;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="kr"&gt;any&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;orders&lt;/span&gt; &lt;span class="o"&gt;=&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;orders&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;priority&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;priority&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;a&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="nf"&gt;localeCompare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&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="p"&gt;);&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;order&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;orders&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="s2"&gt;`validate-&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;id&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="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;order&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="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Missing order id&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;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="s2"&gt;`charge-&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;id&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="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;chargeCard&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;idempotencyKey&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;id&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;

      &lt;span class="k"&gt;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="s2"&gt;`notify-&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;id&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="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;sendConfirmation&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;idempotencyKey&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;id&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;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;processed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&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;h2&gt;
  
  
  Durable-safe handler using &lt;code&gt;context.map()&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;context.map()&lt;/code&gt; changes the shape of the problem a bit. Each item becomes its own durable unit of work.&lt;/p&gt;

&lt;p&gt;That matters because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Failures are isolated to a single item
&lt;/li&gt;
&lt;li&gt;Completed items don’t get re-run because something else failed
&lt;/li&gt;
&lt;li&gt;Concurrency becomes a first-class knob (&lt;code&gt;maxConcurrency&lt;/code&gt;)
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The trade-offs are real too:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Large lists can emit a lot of steps quickly
&lt;/li&gt;
&lt;li&gt;Strict sequencing is harder to express
&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="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;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="kr"&gt;any&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;orders&lt;/span&gt; &lt;span class="o"&gt;=&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;orders&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;priority&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;priority&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;a&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="nf"&gt;localeCompare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&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="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mapResult&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;process-orders&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;orders&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="na"&gt;ctx&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="na"&gt;order&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&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;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;`validate-&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;id&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="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;order&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="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Missing order id&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;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;`charge-&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;id&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="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;chargeCard&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;idempotencyKey&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;id&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="k"&gt;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;`notify-&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;id&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="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;sendConfirmation&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;idempotencyKey&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;id&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="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;id&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="s2"&gt;ok&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="na"&gt;maxConcurrency&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;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mapResult&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;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;processed&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="nx"&gt;length&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="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Final takeaway
&lt;/h2&gt;

&lt;p&gt;Durable execution encourages you to slow down just a bit and be explicit about ordering, retries, idempotency, and where work can safely be repeated.&lt;/p&gt;

&lt;p&gt;That’s exactly why this question was worth asking—and why the conversation around it was worth having.&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>code</category>
      <category>lambda</category>
      <category>durable</category>
    </item>
    <item>
      <title>AWS Lambda Durable Functions: Build Workflows That Last</title>
      <dc:creator>Eric D Johnson</dc:creator>
      <pubDate>Wed, 03 Dec 2025 21:33:02 +0000</pubDate>
      <link>https://forem.com/aws/aws-lambda-durable-functions-build-workflows-that-last-3ac7</link>
      <guid>https://forem.com/aws/aws-lambda-durable-functions-build-workflows-that-last-3ac7</guid>
      <description>&lt;p&gt;&lt;em&gt;Long-running workflows without managing infrastructure&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Your Lambda function needs to wait for a human approval. Or retry a failed API call with exponential backoff. Or orchestrate multiple steps that span hours. How do you build that without managing servers or databases?&lt;/p&gt;

&lt;p&gt;AWS Lambda Durable Functions solve this. Write your workflow in your programming language—Node.js, TypeScript, Python, with more coming—using straightforward async code. Lambda handles the rest: checkpointing state, resuming after waits, retrying issues, and scaling automatically. Workflows can run for up to a year, and you only pay for actual execution time—not while waiting.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Are Durable Functions?
&lt;/h2&gt;

&lt;p&gt;

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


&lt;br&gt;
Durable functions are Lambda functions that can pause and resume. When your function waits for a callback or sleeps for an hour, Lambda checkpoints its state and stops execution. When it's time to continue, Lambda resumes exactly where it left off—with all variables and context intact.&lt;/p&gt;

&lt;p&gt;This isn't a new compute model. It's regular Lambda with automatic state management. You write normal async/await code. Lambda makes it durable.&lt;/p&gt;
&lt;h2&gt;
  
  
  A Simple Example
&lt;/h2&gt;

&lt;p&gt;Here's a workflow that creates an order, waits 5 minutes, then sends a notification:&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;DurableContext&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="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;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="kr"&gt;any&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;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;create-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="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="nf"&gt;createOrder&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="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="na"&gt;seconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;300&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;send-notification&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="nf"&gt;sendEmail&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;customerId&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;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="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;id&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;completed&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;That's it. No state machines to configure, no databases to manage, no polling loops. The function pauses during the wait, costs nothing while idle, and resumes automatically after 5 minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Capabilities
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Long execution times&lt;/strong&gt; - Functions can run for up to 1 year. Individual invocations are still limited to 15 minutes, but the workflow continues across multiple invocations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Automatic checkpointing&lt;/strong&gt; - Lambda saves your function's state at each step. If something fails, the function resumes from the last checkpoint—not from the beginning.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Built-in retries&lt;/strong&gt; - Configure retry strategies with exponential backoff. Lambda handles the retry logic and timing automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wait for callbacks&lt;/strong&gt; - Pause execution until an external event arrives. Perfect for human approvals, webhook responses, or async API results.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Parallel execution&lt;/strong&gt; - Run multiple operations concurrently and wait for all to complete. Lambda manages the coordination.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nested workflows&lt;/strong&gt; - Invoke other durable functions and compose complex workflows from simple building blocks.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Works: The Replay Model
&lt;/h2&gt;

&lt;p&gt;Durable functions use a replay-based execution model. When your function resumes, Lambda replays it from the start—but instead of re-executing operations, it uses checkpointed results.&lt;/p&gt;

&lt;p&gt;Here's what happens:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;First invocation&lt;/strong&gt; - Your function runs, executing each step and checkpointing results&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wait or callback&lt;/strong&gt; - Function pauses, Lambda saves state and stops execution&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resume&lt;/strong&gt; - Lambda invokes your function again, replaying from the start&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Replay&lt;/strong&gt; - Operations return checkpointed results instantly instead of re-executing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Continue&lt;/strong&gt; - Function continues past the wait with all context intact&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This model ensures your function always sees consistent state, even across issues and restarts. Operations are deterministic—they execute once and replay with the same result.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Learn more:&lt;/strong&gt; &lt;a href="https://dev.to/aws/the-replay-model-how-aws-lambda-durable-functions-actually-work-2a79"&gt;Understanding the Replay Model&lt;/a&gt; explains how replay works, why operations must be deterministic, and how to handle non-deterministic code safely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Use Cases
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Approval workflows&lt;/strong&gt; - Wait for human approval before proceeding. The function pauses until someone clicks approve or reject.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Saga patterns&lt;/strong&gt; - Coordinate distributed transactions with compensating actions. If a step fails, automatically roll back previous steps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scheduled tasks&lt;/strong&gt; - Wait for specific times or intervals. Process data at midnight, send reminders after 24 hours, or retry every 5 minutes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;API orchestration&lt;/strong&gt; - Call multiple APIs with retries and error handling. Coordinate responses and handle partial issues gracefully.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data processing pipelines&lt;/strong&gt; - Process large datasets in stages with checkpoints. Resume from the last successful stage if something fails.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Event-driven workflows&lt;/strong&gt; - React to external events like webhooks, IoT signals, or user actions. Wait for events and continue processing when they arrive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing Your Workflows
&lt;/h2&gt;

&lt;p&gt;Testing long-running workflows doesn't mean waiting hours. The Durable Execution SDK includes a testing library that runs your functions locally in milliseconds:&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="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="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;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;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="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="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;getResult&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;123&lt;/span&gt;&lt;span class="dl"&gt;'&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;completed&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;The test runner simulates checkpoints, skips time-based waits, and lets you inspect every operation. You can test callbacks, retries, and issues without deploying to AWS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Learn more:&lt;/strong&gt; &lt;a href="https://dev.to/aws/testing-aws-lambda-durable-functions-in-typescript-5bj2"&gt;Testing Durable Functions&lt;/a&gt; covers local testing, cloud integration tests, debugging techniques, and best practices.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying with AWS SAM
&lt;/h2&gt;

&lt;p&gt;Deploy durable functions using AWS SAM with a few key configurations:&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;OrderProcessorFunction&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::Serverless::Function&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;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;src/order-processor&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;index.handler&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;DurableConfig&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;ExecutionTimeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;900&lt;/span&gt;
        &lt;span class="na"&gt;RetentionPeriodInDays&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;7&lt;/span&gt;
    &lt;span class="na"&gt;Metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;BuildMethod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;esbuild&lt;/span&gt;
      &lt;span class="na"&gt;BuildProperties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;EntryPoints&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;index.ts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;DurableConfig&lt;/code&gt; property enables durable execution and sets the workflow timeout. SAM automatically handles IAM permissions for checkpointing and state management.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Learn more:&lt;/strong&gt; &lt;a href="https://dev.to/aws/developing-aws-lambda-durable-functions-with-aws-sam-ga9"&gt;Deploying Durable Functions with SAM&lt;/a&gt; covers template configuration, permissions, build settings, and deployment best practices.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to Use Durable Functions
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Your workflow spans multiple steps with waits or callbacks&lt;/li&gt;
&lt;li&gt;You need automatic retries with exponential backoff&lt;/li&gt;
&lt;li&gt;You want to coordinate multiple async operations&lt;/li&gt;
&lt;li&gt;Your process requires human approval or external events&lt;/li&gt;
&lt;li&gt;You need to handle long-running tasks without managing state&lt;/li&gt;
&lt;li&gt;You prefer writing workflows as code rather than configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Install the SDK&lt;/strong&gt;: &lt;code&gt;npm install @aws/durable-execution-sdk-js&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write your function&lt;/strong&gt;: Wrap your handler with &lt;code&gt;withDurableExecution()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use durable operations&lt;/strong&gt;: &lt;code&gt;context.step()&lt;/code&gt;, &lt;code&gt;context.wait()&lt;/code&gt;, &lt;code&gt;context.waitForCallback()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test locally&lt;/strong&gt;: Use &lt;code&gt;LocalDurableTestRunner&lt;/code&gt; for fast iteration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deploy with SAM&lt;/strong&gt;: Add &lt;code&gt;DurableConfig&lt;/code&gt; to your template&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitor execution&lt;/strong&gt;: Use Amazon CloudWatch and AWS X-Ray for observability&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Learn More
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://dev.to/aws/the-replay-model-how-aws-lambda-durable-functions-actually-work-2a79"&gt;Understanding the Replay Model&lt;/a&gt;&lt;/strong&gt; - Deep dive into how durable functions work under the hood&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://dev.to/aws/testing-aws-lambda-durable-functions-in-typescript-5bj2"&gt;Testing Durable Functions&lt;/a&gt;&lt;/strong&gt; - Comprehensive guide to testing workflows locally and in the cloud&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://dev.to/aws/developing-aws-lambda-durable-functions-with-aws-sam-ga9"&gt;Deploying with AWS SAM&lt;/a&gt;&lt;/strong&gt; - Complete deployment guide with templates and best practices&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;AWS Lambda Durable Functions let you build long-running workflows without managing infrastructure. Write straightforward async code, and Lambda handles state management, retries, and resumption. Your functions can wait for callbacks, retry issues, and run for up to a year—all while paying only for execution time.&lt;/p&gt;

&lt;p&gt;Start with simple workflows, test locally for fast iteration, and deploy with confidence knowing Lambda manages the complexity of distributed state.&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>aws</category>
      <category>lambda</category>
      <category>durable</category>
    </item>
    <item>
      <title>Developing AWS Lambda Durable Functions with AWS SAM</title>
      <dc:creator>Eric D Johnson</dc:creator>
      <pubDate>Wed, 03 Dec 2025 21:25:35 +0000</pubDate>
      <link>https://forem.com/aws/developing-aws-lambda-durable-functions-with-aws-sam-ga9</link>
      <guid>https://forem.com/aws/developing-aws-lambda-durable-functions-with-aws-sam-ga9</guid>
      <description>&lt;p&gt;&lt;em&gt;How to configure, build, and deploy long-running workflows using SAM templates&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You've written a durable function that orchestrates a multi-step workflow. Now you may want to deploy it. AWS Serverless Application Model (AWS SAM) makes this straightforward with specific configurations in your template. Let's walk through how to set up durable functions effectively.&lt;/p&gt;

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

&lt;p&gt;You'll need AWS SAM CLI version 1.150.1 or greater. Check your version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sam &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you need to upgrade, follow the &lt;a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html" rel="noopener noreferrer"&gt;AWS SAM installation guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Basics: Enabling Durable Functions
&lt;/h2&gt;

&lt;p&gt;To make an AWS Lambda function durable, add the &lt;code&gt;DurableConfig&lt;/code&gt; property to your AWS SAM template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;OrderProcessorFunction&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::Serverless::Function&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;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;src/order-processor&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;index.handler&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;Architectures&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;arm64&lt;/span&gt;
      &lt;span class="na"&gt;Timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;900&lt;/span&gt;                &lt;span class="c1"&gt;# Function timeout: 15 minutes&lt;/span&gt;
      &lt;span class="na"&gt;DurableConfig&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;ExecutionTimeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3600&lt;/span&gt;    &lt;span class="c1"&gt;# Execution timeout: 1 hour&lt;/span&gt;
        &lt;span class="na"&gt;RetentionPeriodInDays&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;7&lt;/span&gt;  &lt;span class="c1"&gt;# Keep execution history for 7 days&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Understanding the two timeout settings is crucial:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Function Timeout&lt;/strong&gt; (&lt;code&gt;Timeout&lt;/code&gt;) controls how long each individual Lambda invocation can run. This is still capped at 15 minutes (900 seconds), just like regular Lambda functions. Each time your durable function checkpoints and resumes, it's a new invocation with its own 15-minute window.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Execution Timeout&lt;/strong&gt; (&lt;code&gt;ExecutionTimeout&lt;/code&gt;) controls how long the entire workflow can run across all invocations. This can be up to 1 year. Your workflow can pause, wait for callbacks, and resume many times, as long as the total elapsed time doesn't exceed this limit.&lt;/p&gt;

&lt;p&gt;For long-running workflows where the execution timeout exceeds the function timeout, you must invoke the function asynchronously. Synchronous invocations will fail with a validation error if the execution timeout is longer than the function timeout.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing the SDK
&lt;/h2&gt;

&lt;p&gt;Add the durable execution SDK to your function's dependency manifest. SAM will automatically install it during the build process.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TypeScript/JavaScript&lt;/strong&gt; - Add to &lt;code&gt;package.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@aws/durable-execution-sdk-js"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^1.0.0"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Python&lt;/strong&gt; - Add to &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;aws-durable-execution-sdk-python
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For client functions that send callbacks or query execution state, add the AWS SDK:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TypeScript/JavaScript:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@aws-sdk/client-lambda"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^3.0.0"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Build Configuration
&lt;/h2&gt;

&lt;p&gt;Configure SAM to build your TypeScript functions with esbuild:&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;OrderProcessorFunction&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::Serverless::Function&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;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;src/order-processor&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;index.handler&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;Architectures&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;arm64&lt;/span&gt;
      &lt;span class="na"&gt;DurableConfig&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;ExecutionTimeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3600&lt;/span&gt;
        &lt;span class="na"&gt;RetentionPeriodInDays&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;7&lt;/span&gt;
      &lt;span class="na"&gt;Metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;BuildMethod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;esbuild&lt;/span&gt;
        &lt;span class="na"&gt;BuildProperties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;EntryPoints&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;index.ts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SAM will automatically compile your TypeScript code and bundle dependencies into a deployment package.&lt;/p&gt;

&lt;h2&gt;
  
  
  IAM Permissions
&lt;/h2&gt;

&lt;p&gt;AWS SAM automatically grants the necessary permissions for durable functions to checkpoint and manage their execution state. You don't need to explicitly configure these permissions.&lt;/p&gt;

&lt;p&gt;However, you may want to configure permissions for client functions that interact with durable executions. For functions that send callbacks:&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;BaristaCallbackFunction&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::Serverless::Function&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;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;src/barista-callback&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;index.handler&lt;/span&gt;
    &lt;span class="na"&gt;Policies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Statement&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Effect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Allow&lt;/span&gt;
            &lt;span class="na"&gt;Action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;lambda:SendDurableExecutionCallbackSuccess&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;lambda:SendDurableExecutionCallbackFailure&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;lambda:SendDurableExecutionCallbackHeartbeat&lt;/span&gt;
            &lt;span class="na"&gt;Resource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Sub&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${CoffeeOrderFunction}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For functions that monitor execution state or stop executions:&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;MonitoringFunction&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::Serverless::Function&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;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;src/monitoring&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;index.handler&lt;/span&gt;
    &lt;span class="na"&gt;Policies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Statement&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Effect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Allow&lt;/span&gt;
            &lt;span class="na"&gt;Action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;lambda:GetDurableExecution&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;lambda:GetDurableExecutionHistory&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;lambda:ListDurableExecutionsByFunction&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;lambda:SendDurableExecutionCallbackSuccess&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;lambda:SendDurableExecutionCallbackHeartbeat&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;lambda:SendDurableExecutionCallbackFailure&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;lambda:StopDurableExecution&lt;/span&gt;
            &lt;span class="na"&gt;Resource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Sub&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${CoffeeOrderFunction}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These permissions allow client functions to interact with your durable workflows by sending callbacks, monitoring execution state, or stopping running executions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Globals for Common Configuration
&lt;/h2&gt;

&lt;p&gt;AWS SAM's &lt;code&gt;Globals&lt;/code&gt; section reduces repetition when you have multiple functions with shared settings:&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;Globals&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Function&lt;/span&gt;&lt;span class="pi"&gt;:&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;Architectures&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;arm64&lt;/span&gt;
    &lt;span class="na"&gt;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;MemorySize&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;512&lt;/span&gt;
    &lt;span class="na"&gt;Tracing&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Active&lt;/span&gt;

&lt;span class="na"&gt;Resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;OrderProcessorFunction&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::Serverless::Function&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;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;src/order-processor&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;index.handler&lt;/span&gt;
      &lt;span class="na"&gt;DurableConfig&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;ExecutionTimeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3600&lt;/span&gt;
        &lt;span class="na"&gt;RetentionPeriodInDays&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;7&lt;/span&gt;

  &lt;span class="na"&gt;CallbackHandlerFunction&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::Serverless::Function&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;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;src/callback-handler&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;index.handler&lt;/span&gt;
      &lt;span class="c1"&gt;# No DurableConfig - this is a regular Lambda function&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can set common runtime, architecture, and memory settings at the global level. For &lt;code&gt;DurableConfig&lt;/code&gt;, configure it at the function level to make it explicit which functions are durable. Functions without &lt;code&gt;DurableConfig&lt;/code&gt; are regular Lambda functions.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Complete Example
&lt;/h2&gt;

&lt;p&gt;Here's a full AWS SAM template for a coffee ordering system with durable workflows:&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;AWSTemplateFormatVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2010-09-09'&lt;/span&gt;
&lt;span class="na"&gt;Transform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Serverless-2016-10-31&lt;/span&gt;
&lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Coffee Ordering System with Durable Functions&lt;/span&gt;

&lt;span class="na"&gt;Globals&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Function&lt;/span&gt;&lt;span class="pi"&gt;:&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;Architectures&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;arm64&lt;/span&gt;
    &lt;span class="na"&gt;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;MemorySize&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;512&lt;/span&gt;
    &lt;span class="na"&gt;Tracing&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Active&lt;/span&gt;

&lt;span class="na"&gt;Resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# ========== AMAZON DYNAMODB ==========&lt;/span&gt;

  &lt;span class="na"&gt;OrdersTable&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::DynamoDB::Table&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;TableName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CoffeeOrders&lt;/span&gt;
      &lt;span class="na"&gt;BillingMode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PAY_PER_REQUEST&lt;/span&gt;
      &lt;span class="na"&gt;AttributeDefinitions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;AttributeName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;orderId&lt;/span&gt;
          &lt;span class="na"&gt;AttributeType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;S&lt;/span&gt;
      &lt;span class="na"&gt;KeySchema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;AttributeName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;orderId&lt;/span&gt;
          &lt;span class="na"&gt;KeyType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HASH&lt;/span&gt;

  &lt;span class="c1"&gt;# ========== FUNCTIONS ==========&lt;/span&gt;

  &lt;span class="na"&gt;CoffeeOrderFunction&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::Serverless::Function&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;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;src/coffee-order&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;index.handler&lt;/span&gt;
      &lt;span class="na"&gt;AutoPublishAlias&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Live&lt;/span&gt;
      &lt;span class="na"&gt;DurableConfig&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;ExecutionTimeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3600&lt;/span&gt;
        &lt;span class="na"&gt;RetentionPeriodInDays&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;7&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;Variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;ORDERS_TABLE&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;OrdersTable&lt;/span&gt;
      &lt;span class="na"&gt;Policies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;DynamoDBCrudPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;TableName&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;OrdersTable&lt;/span&gt;
      &lt;span class="na"&gt;Events&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;ApiEvent&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;Api&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;Path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/orders&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;span class="na"&gt;Metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;BuildMethod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;esbuild&lt;/span&gt;
        &lt;span class="na"&gt;BuildProperties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;EntryPoints&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;index.ts&lt;/span&gt;

  &lt;span class="na"&gt;BaristaCallbackFunction&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::Serverless::Function&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;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;src/barista-callback&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;index.handler&lt;/span&gt;
      &lt;span class="na"&gt;Policies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Statement&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Effect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Allow&lt;/span&gt;
              &lt;span class="na"&gt;Action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;lambda:SendDurableExecutionCallbackSuccess&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;lambda:SendDurableExecutionCallbackFailure&lt;/span&gt;
              &lt;span class="na"&gt;Resource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Sub&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${CoffeeOrderFunction}'&lt;/span&gt;
      &lt;span class="na"&gt;Events&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;ApiEvent&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;Api&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;Path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/barista/accept/{orderId}&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;span class="na"&gt;Metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;BuildMethod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;esbuild&lt;/span&gt;
        &lt;span class="na"&gt;BuildProperties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;EntryPoints&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;index.ts&lt;/span&gt;

&lt;span class="na"&gt;Outputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ApiUrl&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;API Gateway endpoint URL&lt;/span&gt;
    &lt;span class="na"&gt;Value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Sub&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod'&lt;/span&gt;

  &lt;span class="na"&gt;CoffeeOrderFunctionArn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Coffee Order Function ARN&lt;/span&gt;
    &lt;span class="na"&gt;Value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;CoffeeOrderFunction.Arn&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Building and Deploying
&lt;/h2&gt;

&lt;p&gt;Build your application with AWS 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;This builds your functions and layers. AWS SAM automatically runs the Makefiles for your layers and uses esbuild for your TypeScript functions.&lt;/p&gt;

&lt;p&gt;Deploy to AWS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sam deploy &lt;span class="nt"&gt;--guided&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;--guided&lt;/code&gt; flag walks you through configuration options. After the first deployment, AWS SAM saves your settings in &lt;code&gt;samconfig.toml&lt;/code&gt;, so subsequent deploys are just:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Local Testing
&lt;/h2&gt;

&lt;p&gt;Test your durable function locally before deploying:&lt;br&gt;
&lt;/p&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;invoke CoffeeOrderFunction &lt;span class="nt"&gt;--event&lt;/span&gt; events/order.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The event file contains your test payload:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"orderId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"test-123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"attendeeId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user-456"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"orderDetails"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"drinkType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Latte"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Grande"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Accessing Cloud Resources Locally
&lt;/h3&gt;

&lt;p&gt;When testing locally, you often need to access deployed AWS resources like Amazon DynamoDB tables or Amazon EventBridge buses. Use the &lt;code&gt;--env-vars&lt;/code&gt; flag with a JSON file to provide environment variables and credentials:&lt;br&gt;
&lt;/p&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;invoke CoffeeOrderFunction &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--event&lt;/span&gt; events/order.json &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--env-vars&lt;/span&gt; locals.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a &lt;code&gt;locals.json&lt;/code&gt; file with environment variables for each function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"CoffeeOrderFunction"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"AWS_REGION"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ORDERS_TABLE_NAME"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CoffeeOrders"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"EVENT_BUS_NAME"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CoffeeOrderingEventBus"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"BaristaCallbackFunction"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"AWS_REGION"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ORDERS_TABLE_NAME"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CoffeeOrders"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows your local function to interact with deployed resources in AWS, assuming your AWS credentials have the necessary permissions. Your function code can access these variables through &lt;code&gt;process.env&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tableName&lt;/span&gt; &lt;span class="o"&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;ORDERS_TABLE_NAME&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;region&lt;/span&gt; &lt;span class="o"&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;AWS_REGION&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Tracking Execution State
&lt;/h3&gt;

&lt;p&gt;For durable functions, you can track execution state:&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;# Get execution details&lt;/span&gt;
sam &lt;span class="nb"&gt;local &lt;/span&gt;execution get &lt;span class="nv"&gt;$EXECUTION_ARN&lt;/span&gt;

&lt;span class="c"&gt;# View execution history&lt;/span&gt;
sam &lt;span class="nb"&gt;local &lt;/span&gt;execution &lt;span class="nb"&gt;history&lt;/span&gt; &lt;span class="nv"&gt;$EXECUTION_ARN&lt;/span&gt;

&lt;span class="c"&gt;# Stop a running execution&lt;/span&gt;
sam &lt;span class="nb"&gt;local &lt;/span&gt;execution stop &lt;span class="nv"&gt;$EXECUTION_ARN&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test callbacks locally:&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;# Send success callback&lt;/span&gt;
sam &lt;span class="nb"&gt;local &lt;/span&gt;callback succeed &lt;span class="nv"&gt;$CALLBACK_ID&lt;/span&gt; &lt;span class="nt"&gt;--result&lt;/span&gt; &lt;span class="s1"&gt;'{"status": "accepted"}'&lt;/span&gt;

&lt;span class="c"&gt;# Send failure callback&lt;/span&gt;
sam &lt;span class="nb"&gt;local &lt;/span&gt;callback fail &lt;span class="nv"&gt;$CALLBACK_ID&lt;/span&gt; &lt;span class="nt"&gt;--error&lt;/span&gt; &lt;span class="s1"&gt;'{"message": "Rejected"}'&lt;/span&gt;

&lt;span class="c"&gt;# Send heartbeat&lt;/span&gt;
sam &lt;span class="nb"&gt;local &lt;/span&gt;callback heartbeat &lt;span class="nv"&gt;$CALLBACK_ID&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Remote Testing
&lt;/h2&gt;

&lt;p&gt;After deploying, test against your live functions:&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;# Invoke the function (automatically uses $LATEST qualifier)&lt;/span&gt;
sam remote invoke CoffeeOrderFunction &lt;span class="nt"&gt;--event&lt;/span&gt; events/order.json

&lt;span class="c"&gt;# Invoke a specific qualifier/alias&lt;/span&gt;
sam remote invoke CoffeeOrderFunction &lt;span class="nt"&gt;--event&lt;/span&gt; events/order.json &lt;span class="nt"&gt;--parameter&lt;/span&gt; &lt;span class="nv"&gt;Qualifier&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;prod

&lt;span class="c"&gt;# Get execution details&lt;/span&gt;
sam remote execution get &lt;span class="nv"&gt;$EXECUTION_ARN&lt;/span&gt;

&lt;span class="c"&gt;# View execution history&lt;/span&gt;
sam remote execution &lt;span class="nb"&gt;history&lt;/span&gt; &lt;span class="nv"&gt;$EXECUTION_ARN&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default, &lt;code&gt;sam remote invoke&lt;/code&gt; uses the &lt;code&gt;$LATEST&lt;/code&gt; qualifier. You can override this with &lt;code&gt;--parameter Qualifier=&amp;lt;your-qualifier&amp;gt;&lt;/code&gt; to test against a specific version or alias.&lt;/p&gt;

&lt;p&gt;The execution history shows every step, checkpoint, and state transition. It's invaluable for debugging workflows in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuration Best Practices
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;ExecutionTimeout&lt;/strong&gt; can be up to 1 year (31,536,000 seconds) and controls how long your entire workflow can run across all invocations. For a coffee order that waits up to 5 minutes for barista acceptance, consider setting it to 600 seconds (10 minutes) to allow some buffer. For document processing that might take hours or days, you can set it much higher - up to the 1-year maximum.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Function Timeout&lt;/strong&gt; is still capped at 15 minutes (900 seconds) and controls how long each individual invocation can run. Set this based on how long your function needs between checkpoints. If your steps typically complete in seconds, starting with 30 seconds is reasonable. If you have longer-running operations, consider increasing it up to the 15-minute maximum.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Async vs Sync Invocation:&lt;/strong&gt; If your execution timeout exceeds your function timeout, you must use asynchronous invocation. Synchronous invocations will fail validation when the execution timeout is longer than the function timeout. Configure your event sources (API Gateway, EventBridge, etc.) to invoke asynchronously for long-running workflows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RetentionPeriodInDays&lt;/strong&gt; determines how long execution history is kept. Consider using 7-30 days for development environments where you're actively debugging. For production environments where you might need to investigate issues weeks later, consider using 90+ days.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Memory&lt;/strong&gt; affects both performance and cost. Starting with 512 MB and adjusting based on your function's needs is a common approach. Durable functions checkpoint frequently, so memory usage is typically lower than equivalent non-durable functions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitoring and Observability
&lt;/h2&gt;

&lt;p&gt;Enable AWS X-Ray tracing to see how your workflow executes:&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;Globals&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Function&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Tracing&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Active&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This traces each invocation and shows you the complete execution path across multiple invocations.&lt;/p&gt;

&lt;p&gt;Add Amazon CloudWatch Logs Insights queries to analyze execution patterns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;filter&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="k"&gt;like&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="k"&gt;CHECKPOINT&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;sort&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;timestamp&lt;/span&gt; &lt;span class="k"&gt;desc&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;limit&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create CloudWatch alarms for execution failures:&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;ExecutionFailureAlarm&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::CloudWatch::Alarm&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;AlarmName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CoffeeOrderExecutionFailures&lt;/span&gt;
    &lt;span class="na"&gt;MetricName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Errors&lt;/span&gt;
    &lt;span class="na"&gt;Namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS/Lambda&lt;/span&gt;
    &lt;span class="na"&gt;Statistic&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Sum&lt;/span&gt;
    &lt;span class="na"&gt;Period&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;300&lt;/span&gt;
    &lt;span class="na"&gt;EvaluationPeriods&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
    &lt;span class="na"&gt;Threshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
    &lt;span class="na"&gt;ComparisonOperator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;GreaterThanThreshold&lt;/span&gt;
    &lt;span class="na"&gt;Dimensions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;FunctionName&lt;/span&gt;
        &lt;span class="na"&gt;Value&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;CoffeeOrderFunction&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Common Patterns
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Event-driven workflows&lt;/strong&gt; triggered by API Gateway, EventBridge, or Amazon Simple Queue Service (Amazon SQS) - useful for order processing, approval workflows, and asynchronous task execution:&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;CoffeeOrderFunction&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::Serverless::Function&lt;/span&gt;
  &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# ... other properties&lt;/span&gt;
    &lt;span class="na"&gt;Events&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;ApiTrigger&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;Api&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;Path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/orders&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;span class="na"&gt;EventBridgeTrigger&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;EventBridgeRule&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;Pattern&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;coffee.orders&lt;/span&gt;
            &lt;span class="na"&gt;detail-type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;OrderPlaced&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For long-running workflows, configure API Gateway to invoke asynchronously:&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;CoffeeOrderFunction&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::Serverless::Function&lt;/span&gt;
  &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# ... other properties&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;900&lt;/span&gt;  &lt;span class="c1"&gt;# 15 minutes&lt;/span&gt;
    &lt;span class="na"&gt;DurableConfig&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;ExecutionTimeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;86400&lt;/span&gt;  &lt;span class="c1"&gt;# 24 hours&lt;/span&gt;
    &lt;span class="na"&gt;Events&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;ApiTrigger&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;Api&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;Path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/orders&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;span class="na"&gt;RequestParameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;method.request.header.X-Amz-Invocation-Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;Required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
                &lt;span class="na"&gt;Caching&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Scheduled workflows&lt;/strong&gt; that run on a timer - useful for daily reports, periodic data processing, or scheduled maintenance tasks:&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;DailyReportFunction&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::Serverless::Function&lt;/span&gt;
  &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# ... other properties&lt;/span&gt;
    &lt;span class="na"&gt;Events&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;DailySchedule&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;Schedule&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;Schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cron(0 9 * * ? *)&lt;/span&gt;  &lt;span class="c1"&gt;# 9 AM daily&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Fan-out workflows&lt;/strong&gt; that process items in parallel - useful for batch processing, data transformation, or concurrent API calls:&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;BatchProcessorFunction&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::Serverless::Function&lt;/span&gt;
  &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# ... other properties&lt;/span&gt;
    &lt;span class="na"&gt;ReservedConcurrentExecutions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;  &lt;span class="c1"&gt;# Limit parallel executions&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;&lt;strong&gt;Validation error on invoke:&lt;/strong&gt; If you get a validation error saying "Execution timeout must be within the function timeout," you're trying to synchronously invoke a function where the execution timeout exceeds the function timeout. Use asynchronous invocation for long-running workflows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Individual invocation timeout:&lt;/strong&gt; If your function times out during a single invocation, consider increasing the &lt;code&gt;Timeout&lt;/code&gt; property. Start by doubling the current value, up to the maximum of 900 seconds. This is separate from the workflow's total &lt;code&gt;ExecutionTimeout&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Workflow timeout:&lt;/strong&gt; If your entire workflow times out, consider increasing the &lt;code&gt;ExecutionTimeout&lt;/code&gt; in &lt;code&gt;DurableConfig&lt;/code&gt;. This can be up to 1 year (31,536,000 seconds).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Permission denied for callbacks:&lt;/strong&gt; Ensure client functions have the correct IAM permissions to interact with durable executions. Functions sending callbacks need &lt;code&gt;SendDurableExecutionCallback*&lt;/code&gt; permissions with the target durable function as the resource.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Deploying durable functions with AWS SAM involves several key configurations: enable &lt;code&gt;DurableConfig&lt;/code&gt; on your function to set execution timeout and retention period, install the durable execution SDK as a dependency, and configure esbuild for TypeScript compilation. AWS SAM facilitates the IAM permissions needed for durable execution operations. For client functions that interact with durable executions, consider explicitly configuring permissions for callback and monitoring operations.&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;Globals&lt;/code&gt; to reduce repetition across functions, set appropriate timeouts for both individual invocations and total workflow duration, and enable tracing for observability. Test locally with &lt;code&gt;sam local invoke&lt;/code&gt; and &lt;code&gt;sam local execution&lt;/code&gt; commands before deploying to AWS.&lt;/p&gt;

&lt;p&gt;With these patterns in place, you can deploy long-running workflows that span hours or days, handle failures gracefully, and provide full visibility into execution state.&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>aws</category>
      <category>durable</category>
      <category>lambda</category>
    </item>
    <item>
      <title>The Replay Model: How AWS Lambda Durable Functions Actually Work</title>
      <dc:creator>Eric D Johnson</dc:creator>
      <pubDate>Wed, 03 Dec 2025 00:27:54 +0000</pubDate>
      <link>https://forem.com/aws/the-replay-model-how-aws-lambda-durable-functions-actually-work-2a79</link>
      <guid>https://forem.com/aws/the-replay-model-how-aws-lambda-durable-functions-actually-work-2a79</guid>
      <description>&lt;p&gt;&lt;em&gt;Understanding the checkpoint-based execution that makes long-running workflows possible&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You write an AWS Lambda function that looks like it runs continuously for hours. But Lambda functions can only run for 15 minutes. How does this work?&lt;/p&gt;

&lt;p&gt;The answer is &lt;strong&gt;replay&lt;/strong&gt; - a checkpoint-based execution model that makes your function restart from the beginning on every invocation, but skip the work it's already done. It's elegant, efficient, and once you understand it, surprisingly intuitive.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Core Principle
&lt;/h2&gt;

&lt;p&gt;Here's the fundamental truth about durable functions:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Your handler function re-executes from the beginning on every invocation, but completed operations return cached results from checkpoints instead of re-executing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's see this in action with a simple 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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;processOrder&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="kr"&gt;any&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;DurableContext&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;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;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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;create-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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Creating 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;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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;total&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&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;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;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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;process-payment&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Processing payment...&lt;/span&gt;&lt;span class="dl"&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;transactionId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;txn-456&lt;/span&gt;&lt;span class="dl"&gt;'&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;success&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;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;wait&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;seconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="c1"&gt;// Wait 5 minutes&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;notification&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;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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;send-notification&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&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 notification...&lt;/span&gt;&lt;span class="dl"&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;sent&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="k"&gt;return&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;payment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;notification&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's what actually happens across two separate Lambda invocations:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Invocation 1&lt;/strong&gt; (t=0s):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Creating order...
Processing payment...
[Checkpoint: create-order completed]
[Checkpoint: process-payment completed]
[Function terminates - waiting 5 minutes]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Invocation 2&lt;/strong&gt; (t=300s, after wait completes):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[REPLAY MODE: Skipping create-order - returning cached result]
[REPLAY MODE: Skipping process-payment - returning cached result]
[EXECUTION MODE: Running send-notification]
Sending notification...
[Checkpoint: send-notification completed]
[Function completes]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice what happened: The function started from the beginning both times, but on the second invocation, &lt;code&gt;create-order&lt;/code&gt; and &lt;code&gt;process-payment&lt;/code&gt; didn't re-execute. The logs only appeared once, even though the code ran twice. The function seamlessly continued from where it left off.&lt;/p&gt;

&lt;p&gt;This is replay.&lt;/p&gt;

&lt;h2&gt;
  
  
  Execution Modes: The Secret Sauce
&lt;/h2&gt;

&lt;p&gt;The SDK operates in two modes that automatically switch based on what's happening.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ExecutionMode&lt;/strong&gt; is when the function is executing operations for the first time. Operations execute normally, results are saved to checkpoints, logs are emitted, and side effects happen.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ReplayMode&lt;/strong&gt; is when the function is replaying previously completed operations. Operations return cached results instantly without actual execution, logs are suppressed, and no side effects occur.&lt;/p&gt;

&lt;p&gt;The SDK automatically transitions from ReplayMode to ExecutionMode when it reaches an operation that hasn't been completed yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Checkpoints Work
&lt;/h2&gt;

&lt;p&gt;Every operation creates a checkpoint that stores:&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="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;operationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="c1"&gt;// Sequential ID&lt;/span&gt;
  &lt;span class="nx"&gt;operationType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;STEP&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;// STEP, WAIT, INVOKE, etc.&lt;/span&gt;
  &lt;span class="nx"&gt;operationName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;process-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;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SUCCEEDED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c1"&gt;// STARTED, SUCCEEDED, FAILED, PENDING&lt;/span&gt;
  &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;                   &lt;span class="c1"&gt;// The actual return value&lt;/span&gt;
    &lt;span class="nl"&gt;transactionId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;txn-456&lt;/span&gt;&lt;span class="dl"&gt;"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;success&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;When your function restarts, the SDK loads all checkpoints from storage, indexes them by operation ID, returns cached results for completed operations, and executes new operations normally.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Determinism Requirement
&lt;/h2&gt;

&lt;p&gt;For replay to work, your code must be &lt;strong&gt;deterministic&lt;/strong&gt; - the same sequence of operations must happen in the same order every time.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Breaks Determinism
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ Random control flow&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.5&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;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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;optional-step&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="nf"&gt;doSomething&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// First run: random = 0.7, step executes, checkpoint created&lt;/span&gt;
&lt;span class="c1"&gt;// Second run: random = 0.3, step skipped&lt;/span&gt;
&lt;span class="c1"&gt;// Error: Expected operation 'optional-step' at position 2, not found!&lt;/span&gt;

&lt;span class="c1"&gt;// ❌ Time-based branching&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isWeekend&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;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getDay&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&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;isWeekend&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;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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;weekend-task&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="nf"&gt;doWeekendWork&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// First run: Friday (day 5), step executes&lt;/span&gt;
&lt;span class="c1"&gt;// Second run: Monday (day 1), step skipped&lt;/span&gt;
&lt;span class="c1"&gt;// Error: Replay consistency violation!&lt;/span&gt;

&lt;span class="c1"&gt;// ❌ External state&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;step1&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;counter&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Won't increment during replay!&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;counter&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;
  
  
  How to Write Deterministic Code
&lt;/h3&gt;

&lt;p&gt;The rule is simple: &lt;strong&gt;capture non-deterministic values inside steps&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="c1"&gt;// ✅ Capture random values in steps&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;randomId&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;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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;generate-id&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;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randomUUID&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Executed once, cached on replay&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Capture timestamps in steps&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timestamp&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;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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;get-timestamp&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="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="c1"&gt;// Same timestamp on every replay&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Use event data for control flow&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;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shouldProcess&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// Deterministic - same event every time&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;process&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="nf"&gt;doWork&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Capture time-based decisions in steps&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isWeekend&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;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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;check-day&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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getDay&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isWeekend&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;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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;weekend-task&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="nf"&gt;doWeekendWork&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;h2&gt;
  
  
  Replay Consistency Validation
&lt;/h2&gt;

&lt;p&gt;The SDK validates that operations occur in the same order on every invocation:&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;// What gets validated:&lt;/span&gt;
&lt;span class="c1"&gt;// 1. Operation type (STEP, WAIT, INVOKE)&lt;/span&gt;
&lt;span class="c1"&gt;// 2. Operation name (your identifier)&lt;/span&gt;
&lt;span class="c1"&gt;// 3. Operation position (sequential order)&lt;/span&gt;

&lt;span class="c1"&gt;// Example validation error:&lt;/span&gt;
&lt;span class="c1"&gt;// "Replay consistency violation: Expected operation 'process-payment' &lt;/span&gt;
&lt;span class="c1"&gt;//  of type STEP at position 2, but found operation 'send-email' of type STEP"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This catches bugs early. If your code's execution path changes between invocations, you'll know immediately.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Complete Example: Order Processing with Replay
&lt;/h2&gt;

&lt;p&gt;Let's examine a potential workflow through multiple invocations:&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;processOrder&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="kr"&gt;any&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;DurableContext&lt;/span&gt;&lt;span class="p"&gt;)&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;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;Order processing started&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;orderId&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;orderId&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Step 1: Validate inventory&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;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="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="nx"&gt;ctx&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;Checking inventory&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;response&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://api.inventory.com/check`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&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="na"&gt;body&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="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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&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;inventory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;available&lt;/span&gt;&lt;span class="p"&gt;)&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;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Out of stock&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;missing&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="nx"&gt;missing&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;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;out-of-stock&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;// Step 2: Process payment&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;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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;process-payment&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;ctx&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;Processing payment&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;amount&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="nx"&gt;total&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;response&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://api.payments.com/charge`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&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="na"&gt;body&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="na"&gt;customerId&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;customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="na"&gt;amount&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="nx"&gt;total&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Step 3: Wait for warehouse confirmation (5 minute timeout)&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;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 warehouse confirmation&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;confirmation&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;ctx&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;warehouse-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;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="c1"&gt;// Send callback ID to warehouse system&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://api.warehouse.com/notify`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;method&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="na"&gt;body&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="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;id&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="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;seconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;300&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;// Step 4: Send notification&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;notify-customer&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;ctx&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 customer notification&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="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://api.notifications.com/send`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&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="na"&gt;body&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="na"&gt;customerId&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;customerId&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Your order is confirmed!&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;ctx&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;Order processing completed&lt;/span&gt;&lt;span class="dl"&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;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;completed&lt;/span&gt;&lt;span class="dl"&gt;'&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;payment&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Invocation Timeline
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Invocation 1&lt;/strong&gt; (t=0s) runs in ExecutionMode. The logs show "Order processing started", "Checking inventory", "Processing payment", and "Waiting for warehouse confirmation". Checkpoints are created for &lt;code&gt;check-inventory&lt;/code&gt; and &lt;code&gt;process-payment&lt;/code&gt;, both marked as SUCCEEDED. The function then enters a waiting state for the callback 'warehouse-confirm', creating a checkpoint to persist the callbackId and set the timer for the timeout.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Invocation 2&lt;/strong&gt; (t=120s, warehouse confirms) starts in ReplayMode and transitions to ExecutionMode. During the replay phase, "Order processing started", "Checking inventory", "Processing payment", and "Waiting for warehouse confirmation" are all suppressed - the inventory and payment steps return their cached results without re-executing. Once the function reaches new operations, it switches to ExecutionMode, checkpoints the callback result, and logs "Sending customer notification" and "Order processing completed". A checkpoint is created for &lt;code&gt;notify-customer&lt;/code&gt; marked as SUCCEEDED, and the function completes.&lt;/p&gt;

&lt;p&gt;Notice how the function ran from the beginning both times, but the inventory and payment APIs were only called once. Logs only appeared once with no duplicates, and the function seamlessly continued after the callback.&lt;/p&gt;

&lt;h2&gt;
  
  
  Operation IDs: The Replay Index
&lt;/h2&gt;

&lt;p&gt;Operations are identified by sequential IDs that determine replay 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="c1"&gt;// Root context operations&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;step1&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;// ID: "1"&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;step2&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;// ID: "2"&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;step3&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;// ID: "3"&lt;/span&gt;

&lt;span class="c1"&gt;// Child context operations (from ctx.runInChildContext)&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;runInChildContext&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;childCtx&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;// This operation gets ID: "4"&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;childCtx&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;child-step1&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;// ID: "4-1"&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;childCtx&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;child-step2&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;// ID: "4-2"&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;IDs are deterministic - they're based on execution order, not operation names. This is why operation order must be consistent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Pitfalls and Solutions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Non-Deterministic Control Flow
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ BAD: Random branching&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.5&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;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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;optional&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="nf"&gt;doWork&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ GOOD: Event-based branching&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;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shouldDoWork&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;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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;optional&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="nf"&gt;doWork&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ GOOD: Capture decision in step&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;shouldDoWork&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;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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;decide&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="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&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="nx"&gt;shouldDoWork&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;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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;optional&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="nf"&gt;doWork&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;
  
  
  2. Mutating Closure Variables
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ BAD: External mutation&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;add-items&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;total&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Won't happen during replay!&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ GOOD: Return values&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;total&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;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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;calculate-total&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="mi"&gt;10&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;
  
  
  3. Side Effects Outside Steps
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ BAD: Direct API calls&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.example.com/data&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;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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;process&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="nf"&gt;processData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="c1"&gt;// API called on every replay!&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ GOOD: API calls inside steps&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&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;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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fetch-data&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="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.example.com/data&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;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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;process&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="nf"&gt;processData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Debugging Replay Issues
&lt;/h2&gt;

&lt;p&gt;When replay goes wrong, use the execution history:&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;# View execution history&lt;/span&gt;
sam remote execution &lt;span class="nb"&gt;history&lt;/span&gt; &lt;span class="nv"&gt;$EXECUTION_ARN&lt;/span&gt;

&lt;span class="c"&gt;# See detailed operation data&lt;/span&gt;
sam remote execution &lt;span class="nb"&gt;history&lt;/span&gt; &lt;span class="nv"&gt;$EXECUTION_ARN&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt; json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The history shows every operation that executed, the order they ran in, their results or errors, and when mode transitions occurred. Look for operations appearing in different orders, missing or extra operations, or operations with different names at the same position.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Practices for Replay-Safe Code
&lt;/h2&gt;

&lt;p&gt;Wrap all non-deterministic operations in steps - random numbers, timestamps, API calls, and database queries should always be inside &lt;code&gt;ctx.step()&lt;/code&gt;. Use event data for control flow rather than runtime-generated values, and never mutate closure variables - return values from steps instead. Keep operation order consistent so the same sequence happens every time. Test with multiple invocations to verify replay behavior locally, and check execution history to debug replay issues quickly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;The replay model is what makes durable functions possible. Your function restarts from the beginning on every invocation, but completed operations return cached results without re-executing. The SDK automatically switches between ReplayMode and ExecutionMode, and your code must be deterministic for replay to work correctly.&lt;/p&gt;

&lt;p&gt;Once you internalize these principles, writing durable functions becomes natural. You write straightforward procedural code, and the SDK handles all the complexity of checkpointing, replay, and state management. The result? Long-running workflows that look like simple functions. That's the magic of replay.&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>durable</category>
      <category>aws</category>
      <category>lambda</category>
    </item>
  </channel>
</rss>
