<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mike Dabydeen</title>
    <description>The latest articles on DEV Community by Mike Dabydeen (@_firelinks).</description>
    <link>https://dev.to/_firelinks</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F84901%2F5b27ca0a-5429-4a17-bb8e-33860dc7678e.jpeg</url>
      <title>DEV Community: Mike Dabydeen</title>
      <link>https://dev.to/_firelinks</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/_firelinks"/>
    <language>en</language>
    <item>
      <title>AI Didn't Make Your Team Faster. It Moved the Bottleneck.</title>
      <dc:creator>Mike Dabydeen</dc:creator>
      <pubDate>Mon, 27 Jul 2026 17:00:00 +0000</pubDate>
      <link>https://dev.to/_firelinks/i-didnt-make-your-team-faster-it-moved-the-bottleneck-1lj</link>
      <guid>https://dev.to/_firelinks/i-didnt-make-your-team-faster-it-moved-the-bottleneck-1lj</guid>
      <description>&lt;p&gt;This is publishing in the same month the loudest AI jobs forecasts got walked back by the people who made them. That reversal surprised a lot of commentators. It shouldn't surprise anyone who measures systems for a living, and the reason why is what this piece is about.&lt;/p&gt;

&lt;p&gt;I've been watching machine learning since 2013, back when Coursera Machine Learning courses meant running gradient descent by hand on a laptop that sounded like it was apologizing. So I want to be careful about how I say this, because it would be easy to read it as a complaint.&lt;/p&gt;

&lt;p&gt;It isn't. AI-assisted development is real, and it's useful. But the story most teams are telling themselves about it is wrong in a specific, measurable way, and the cost of being wrong shows up about ninety days later.&lt;/p&gt;

&lt;p&gt;Here's the claim I keep hearing: AI made our engineers faster, so we're shipping faster. The first half is often true. The second half usually isn't. What actually happened is that the work got faster at one stage and the bottleneck moved somewhere you weren't watching.&lt;/p&gt;

&lt;h2&gt;
  
  
  The number that should bother you
&lt;/h2&gt;

&lt;p&gt;The individual-productivity numbers look great until you put them next to the team numbers.&lt;/p&gt;

&lt;p&gt;A 2025 study of experienced open-source developers found they were about 19% slower on familiar codebases when using AI tools, after predicting the tools would make them 24% faster. Even after finishing, they estimated they'd been sped up by about 20%. Sit with that gap for a second. Not slower than they thought. Slower in absolute terms, while feeling faster. Junior developers, by contrast, picked up real gains, somewhere in the 10 to 30% range on the kind of well-bounded work they were already doing.&lt;/p&gt;

&lt;p&gt;Now zoom out to the team. Coding might improve 30 to 40%, but if planning, testing, and release stay manual, overall throughput often rises less than 10%. The 2026 data circulating right now puts numbers on the side effects: review times stretching, code churn climbing from around 3% to closer to 6%, and AI-generated code carrying materially more security defects, with one analysis citing nearly three times the vulnerability rate. The most common developer complaint isn't "it's wrong." It's "it's almost right, but not quite," which is the most expensive kind of wrong because it survives a casual review.&lt;/p&gt;

&lt;p&gt;None of this means the tools failed. It means we measured the wrong thing and celebrated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is a flow-metrics problem, not an AI problem
&lt;/h2&gt;

&lt;p&gt;I ran a quarter where our deployment frequency was high and our customer satisfaction scores were flat. The pipeline was healthy. The problem selection wasn't. The metric was telling the truth; we were asking it the wrong question.&lt;/p&gt;

&lt;p&gt;AI in the SDLC is the same lesson with a new cause. Deployment frequency and individual coding speed are local optimizations. They tell you how fast one stage runs. They tell you nothing about whether the whole system moves work to "done" any faster. That's what flow efficiency measures, and flow efficiency is where the AI story falls apart.&lt;/p&gt;

&lt;p&gt;When you make the coding stage three times faster, you don't speed up the system. You increase the arrival rate at the next stage. If the next stage is human review, and review capacity is fixed, you've just built a bigger queue. Queuing theory has been clear about this since before any of us were writing software: you cannot speed up a system by accelerating a stage upstream of the constraint. You can only move work to the constraint faster, where it waits.&lt;/p&gt;

&lt;p&gt;So the question every engineering leader should be asking isn't "how much faster is our coding?" It's "where is our constraint now, and did AI move it?" For most teams I talk to, it moved to one of two places: review bandwidth, or requirements quality.&lt;/p&gt;

&lt;h2&gt;
  
  
  The constraint moved to review
&lt;/h2&gt;

&lt;p&gt;Review was always a quiet bottleneck. AI made it loud.&lt;/p&gt;

&lt;p&gt;When a senior engineer reviews human-written code, they're reading the work of someone who had to reason through every line. The reasoning leaves fingerprints. When that same engineer reviews AI-generated code, the fingerprints are gone. The code is plausible, internally consistent, and confidently wrong in places that don't announce themselves. The reviewer can't lean on "would a person have written it this way," because a person didn't.&lt;/p&gt;

&lt;p&gt;That's why experienced developers slow down. The validation burden went up, even though the typing burden went down, and validation is the harder cognitive task. We optimized the easy part and added load to the expensive one.&lt;/p&gt;

&lt;p&gt;The teams handling this well aren't reviewing harder. They're instrumenting the review surface. They treat AI-generated changes as a distinct class with their own telemetry: change failure rate tracked separately for AI-assisted versus human-authored work, churn measured on AI-touched files, and a clear policy on what an agent is allowed to merge without a human in the loop. You can't manage a risk you can't see, and right now most teams can't see which of their incidents trace back to code no human fully reasoned about.&lt;/p&gt;

&lt;h2&gt;
  
  
  The constraint that moved to requirements
&lt;/h2&gt;

&lt;p&gt;The second place the bottleneck landed is further upstream, and it's the more interesting one.&lt;/p&gt;

&lt;p&gt;When implementation was the slow, expensive step, teams could be vague about requirements and absorb the cost later, during the long build, in hallway conversations and mid-sprint corrections. The build was slow enough to hide a lot of ambiguity. Agents removed that slack. When an agent implements a fuzzy requirement in an afternoon, the fuzziness doesn't get caught during a slow build anymore. It ships, fast, and the ambiguity surfaces in production.&lt;/p&gt;

&lt;p&gt;This is why "intent engineering" is becoming a real role rather than a buzzword. The skill that's now scarce isn't writing code. It's writing a specification precise enough that a fast, literal, non-deterministic implementer can't misread it. That's an architecture skill, and it's the one I'd invest in if I were building a team for the next two years.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the classroom shows that the dashboards don't
&lt;/h2&gt;

&lt;p&gt;I teach at local colleges, and the split in the productivity data matches exactly what I see in a lab.&lt;/p&gt;

&lt;p&gt;Give a second-year student an AI assistant and they move faster, because they're doing bounded work where "plausible and almost right" is good enough to learn from. Give the same assistant to someone doing genuinely hard, context-heavy work, and it slows them down, because now they're validating output instead of reasoning forward. The tool is a force multiplier on whatever you already are. It multiplies a junior's output and a senior's review load.&lt;/p&gt;

&lt;p&gt;That has a direct implication for how we build teams, and it's uncomfortable. The traditional path of "juniors write, seniors review" gets worse under AI, not better, because you've increased the volume of code needing senior review while making each review harder. If your plan for AI adoption is "juniors generate more, seniors check it," you've designed a system that maximizes load on your scarcest resource. The teams that get this right are inverting it: seniors use AI to draft and explore at the architecture level, where their judgment catches the failure early, and juniors learn to review before they learn to generate.&lt;/p&gt;

&lt;h2&gt;
  
  
  A note on non-determinism, because I've seen this movie
&lt;/h2&gt;

&lt;p&gt;In the past, I built decentralized peer-to-peer communication infrastructure in the Ethereum era, before most of the current AI conversation existed. We shipped redundancy and assumed it bought us resilience. It didn't, because we hadn't instrumented the system well enough to know which node failed, when, or why. Redundancy without observability is just distributed failure with more moving parts.&lt;/p&gt;

&lt;p&gt;AI agents in the pipeline are a non-deterministic component, and they deserve the same discipline. The same prompt and the same codebase can produce different output on different runs. That means an AI-assisted SDLC needs its own reliability budget, separate from your deterministic services, because the failure modes are different and the blast radius is harder to predict. If you can't answer "which agent produced the change that caused this incident, and can we reproduce the decision," you're operating the way we were at in my past role, and I can tell you how that ends.&lt;/p&gt;

&lt;h2&gt;
  
  
  So what do you actually do
&lt;/h2&gt;

&lt;p&gt;You don't slow down adoption. You change what you measure.&lt;/p&gt;

&lt;p&gt;Stop reporting coding speed as a win on its own. Start tracking flow efficiency end to end, so the queue that AI created at review becomes visible instead of hiding inside a healthy-looking deployment-frequency chart. Separate your change-failure-rate telemetry for AI-assisted work so you can see the real defect cost before it surfaces ninety days out. Invest in the specification skill, because requirements quality is your new constraint whether or not you've named it. And give AI-generated change its own reliability budget, the same way you'd treat any non-deterministic component running in production.&lt;/p&gt;

&lt;p&gt;The honest version of the AI productivity story isn't "it's overhyped" and it isn't "it changes everything." It's that AI made the cheap part of building software cheaper, which means the value, and the risk, both moved to the parts that were always hard: deciding what to build, and verifying that what got built is correct. Those were never the parts a faster typist could fix.&lt;/p&gt;

&lt;p&gt;I've been wrong about technology timelines before. But I'd bet the teams that win the next two years aren't the ones generating the most code. They're the ones who saw the bottleneck move and re-instrumented for where it went.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>devops</category>
      <category>softwareengineering</category>
      <category>management</category>
    </item>
    <item>
      <title>The Missing 20% of Every AI Agents Tutorial — Observability, Fallback, and Cost</title>
      <dc:creator>Mike Dabydeen</dc:creator>
      <pubDate>Thu, 23 Jul 2026 12:00:00 +0000</pubDate>
      <link>https://dev.to/_firelinks/the-missing-20-of-every-ai-agents-tutorial-observability-fallback-and-cost-166l</link>
      <guid>https://dev.to/_firelinks/the-missing-20-of-every-ai-agents-tutorial-observability-fallback-and-cost-166l</guid>
      <description>&lt;p&gt;Every AI agents tutorial walks you through the happy path. Build the loop, connect the tools, watch the agent complete the task. It works. You're impressed. You deploy it.&lt;/p&gt;

&lt;p&gt;Then you find out on a Monday morning that the agent spent $340 over the weekend running a loop it couldn't exit because one of its tool calls kept returning a malformed response.&lt;/p&gt;

&lt;p&gt;That's the 20% nobody covers.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why tutorials skip this
&lt;/h2&gt;

&lt;p&gt;The happy path is teachable in 15 minutes. The production concerns take weeks of running things in production to understand. Tutorial authors don't have your production environment, your tool reliability characteristics, or your actual usage patterns. They can show you the loop. They can't show you what happens when the loop breaks.&lt;/p&gt;

&lt;p&gt;I've been following machine learning and agent research since 2013, through the Coursera era and through several production deployments of non-LLM ML systems before anyone was calling them "agents." What's different now is how accessible the building blocks are. What's the same is that deploying any system that runs autonomously requires you to answer the same operational questions you'd answer for any other production system: how do I know it's working, what happens when it fails, and how much is this going to cost?&lt;/p&gt;

&lt;p&gt;Those questions don't change because the system is powered by a language model.&lt;/p&gt;




&lt;h2&gt;
  
  
  Observability
&lt;/h2&gt;

&lt;p&gt;An agentic system is a distributed system. It makes sequential calls to external services (the model API, your tools, your databases), maintains state across those calls, and makes decisions that affect subsequent steps. You need telemetry on all of it.&lt;/p&gt;

&lt;p&gt;The minimum instrumentation I'd put on any production agent:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Per-run:&lt;/strong&gt; start time, end time, total token consumption (prompt + completion separately), number of tool calls, final status (completed / failed / interrupted), and the full trace of steps taken.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Per tool call:&lt;/strong&gt; tool name, arguments (sanitized for secrets), response status, response latency, and token cost if applicable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Per decision point:&lt;/strong&gt; what the model decided to do next, what alternatives it considered (if your prompting surfaces this), and whether the decision matches expected patterns for this task type.&lt;/p&gt;

&lt;p&gt;The full step trace is the most important piece. When an agent fails, the trace tells you where in the sequence it went wrong. Without it you're debugging from symptoms rather than from cause.&lt;/p&gt;

&lt;p&gt;Most LLM SDKs give you token counts. You have to instrument the rest yourself. A simple structured logger writing to your existing observability stack is enough to start with.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;agentRun&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;runId&lt;/span&gt;&lt;span class="p"&gt;:&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="na"&gt;startTime&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="na"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="na"&gt;tokenConsumption&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;completion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="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;running&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// After each model call:&lt;/span&gt;
&lt;span class="nx"&gt;agentRun&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;steps&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="na"&gt;stepNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;agentRun&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&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="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 'tool_use' or 'text'&lt;/span&gt;
  &lt;span class="na"&gt;toolName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;tokenCost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;input_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;completion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;output_tokens&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="nx"&gt;agentRun&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tokenConsumption&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;input_tokens&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;agentRun&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tokenConsumption&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;completion&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;output_tokens&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Fallback strategy
&lt;/h2&gt;

&lt;p&gt;Three failure modes I've seen in production agentic systems, in order of frequency:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tool call failure.&lt;/strong&gt; The tool returns an error, a malformed response, or nothing. If the agent's loop doesn't have explicit handling for this, it either retries indefinitely, hallucinates a response, or stops with an unhelpful error. All three are worse than a clean fallback.&lt;/p&gt;

&lt;p&gt;The fix is a retry limit with escalation logic. If a tool call fails twice, either fall back to an alternative approach or stop and surface the failure to a human. "Stop and surface" is the right answer more often than people expect.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&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;callToolWithFallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;toolName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;maxRetries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="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;let&lt;/span&gt; &lt;span class="nx"&gt;attempt&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="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;maxRetries&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt;&lt;span class="o"&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;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;toolName&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;maxRetries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Log, alert, and return a structured failure&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;error&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;toolName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;attempt&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;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;requiresHumanReview&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;await&lt;/span&gt; &lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attempt&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="c1"&gt;// backoff&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;&lt;strong&gt;Reasoning loop.&lt;/strong&gt; The agent keeps taking steps without converging on a result. This happens when the task is underspecified, when tool responses are ambiguous, or when the model gets into a pattern where each step looks reasonable but the sequence isn't making progress.&lt;/p&gt;

&lt;p&gt;A step limit is the blunt instrument, and it's the right one. Set a maximum number of steps per run. When the limit is hit, stop, log the full trace, and surface it for human review. You can tune the limit up after you've seen what normal runs look like.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Context window saturation.&lt;/strong&gt; Long-running agents accumulate context. Tool responses, previous steps, and reasoning traces all consume tokens. When you're approaching the context window limit, the model's reasoning quality degrades before you hit a hard error. Budget for this explicitly.&lt;/p&gt;




&lt;h2&gt;
  
  
  Cost management
&lt;/h2&gt;

&lt;p&gt;Token costs compound quickly in agentic systems. Each step requires at minimum a full context injection: all previous steps, all tool definitions, the system prompt. A 10-step agent run at 2,000 tokens per step with claude-opus-4 pricing is not a trivial cost.&lt;/p&gt;

&lt;p&gt;Set a budget per run. Not as a soft suggestion, as a hard limit that terminates the run and logs an alert.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MAX_TOKENS_PER_RUN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// set based on your cost tolerance&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;agentRun&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tokenConsumption&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;agentRun&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tokenConsumption&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;completion&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;MAX_TOKENS_PER_RUN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;agentRun&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;budget_exceeded&lt;/span&gt;&lt;span class="dl"&gt;'&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="s2"&gt;`Run &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;agentRun&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; exceeded token budget. Terminating.`&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;Also: choose the right model for each step. Not every step in an agent loop requires your most capable model. Tool selection and result summarization can often run on cheaper, faster models. Reserve the expensive reasoning for the steps that actually need it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The production checklist I use
&lt;/h2&gt;

&lt;p&gt;Before deploying any agentic workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Step limit defined and enforced&lt;/li&gt;
&lt;li&gt;Token budget defined and enforced&lt;/li&gt;
&lt;li&gt;All tool calls have retry logic with a maximum attempt count&lt;/li&gt;
&lt;li&gt;Full run trace logged to a searchable store&lt;/li&gt;
&lt;li&gt;Alerting on runs that exceed time, cost, or step thresholds&lt;/li&gt;
&lt;li&gt;At least one human review path for runs that fail or exceed thresholds&lt;/li&gt;
&lt;li&gt;Test cases for tool failure scenarios, not just the happy path&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the same checklist I'd use for any autonomous process. The "agent" part doesn't change the operational requirements. It just makes it easier to forget them.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>agents</category>
      <category>architecture</category>
    </item>
    <item>
      <title>From Code to Coaching: Embracing the Growth Mindset in Engineering Management</title>
      <dc:creator>Mike Dabydeen</dc:creator>
      <pubDate>Wed, 27 Nov 2024 03:40:28 +0000</pubDate>
      <link>https://dev.to/_firelinks/from-code-to-coaching-embracing-the-growth-mindset-in-engineering-management-3342</link>
      <guid>https://dev.to/_firelinks/from-code-to-coaching-embracing-the-growth-mindset-in-engineering-management-3342</guid>
      <description>&lt;p&gt;As an engineering manager with over two decades of experience in the industry, I've worn many hats - from network administrator to software developer to college professor. Through it all, I've learned that the most critical skill is not technical expertise, but the ability to lead and empower others. This realization has been the driving force behind my journey from writing code to coaching the next generation of engineers.&lt;/p&gt;

&lt;p&gt;In the early stages of my career, I was laser-focused on honing my technical skills. I could troubleshoot the most complex networking issues, architect robust software systems, and automate tedious tasks with ease. I took immense pride in my ability to solve problems and deliver tangible results. But as I progressed into management roles, I quickly learned that the keys to success were no longer just technical prowess, but the ability to inspire, motivate, and develop my team.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Embracing a Growth Mindset&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The transition from individual contributor to engineering manager was not without its challenges. I had to let go of my ego and the need to be the smartest person in the room. Instead, I had to adopt a growth mindset - the belief that my abilities as a leader were not set in stone, but could be continuously developed and improved.&lt;/p&gt;

&lt;p&gt;This mindset shift was crucial. Rather than viewing my management skills as fixed, I approached each new challenge as an opportunity to learn and grow. I sought out feedback from my team, mentors, and peers, and used it to identify areas for improvement. I experimented with different leadership styles, embraced failures as learning experiences, and constantly sought ways to expand my toolbox.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Empowering Through Coaching&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As I embraced the growth mindset, I also discovered the power of coaching. Instead of simply telling my team members what to do, I learned to ask thought-provoking questions, listen actively, and help them uncover their own solutions. This collaborative approach empowered my engineers to take ownership of their work, develop their problem-solving skills, and reach new levels of performance.&lt;/p&gt;

&lt;p&gt;Coaching has become a cornerstone of my management style. By focusing on my team's growth and development, I've witnessed firsthand the transformative impact it can have. Engineers who were once hesitant and unsure have blossomed into confident, self-directed problem-solvers. Projects that once seemed daunting have been tackled with creativity and enthusiasm.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Rewards of the Journey&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The journey from code to coaching has not been an easy one, but it has been immensely rewarding. I've had to let go of my ego, embrace vulnerability, and continuously challenge myself to improve. But the payoff has been immense - not only in the success of my team, but in my own personal and professional growth.&lt;/p&gt;

&lt;p&gt;As I look back on my 20-year career, I'm grateful for the lessons I've learned and the opportunities I've had to make a difference. I've seen firsthand how a growth mindset and coaching approach can unlock the full potential of an engineering team, driving innovation and transforming organizations.&lt;/p&gt;

&lt;p&gt;And as I add a new role of college professor to my full time career as a software engineering manager, I'm excited to pass on these insights to the next generation of engineers. By instilling a growth mindset and coaching approach, I hope to empower my students to become not just skilled technicians, but inspiring leaders who can drive change and make a lasting impact on the industry.&lt;/p&gt;

&lt;p&gt;The path from code to coaching may not be an easy one, but it is a journey worth taking. By embracing a growth mindset and empowering your team through coaching, you can unlock the full potential of your engineers and create a lasting impact on your organization and the industry as a whole. It's a transformation that has been at the heart of my own career, and one that I'm passionate about sharing with others.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>The Double-Edged Sword of Microservices: Balancing Abstraction and Complexity</title>
      <dc:creator>Mike Dabydeen</dc:creator>
      <pubDate>Wed, 27 Nov 2024 03:26:02 +0000</pubDate>
      <link>https://dev.to/_firelinks/the-double-edged-sword-of-microservices-balancing-abstraction-and-complexity-36io</link>
      <guid>https://dev.to/_firelinks/the-double-edged-sword-of-microservices-balancing-abstraction-and-complexity-36io</guid>
      <description>&lt;p&gt;As software developers, we're always looking for ways to make our systems more scalable, flexible, and resilient. One approach that's gained popularity in recent years is microservices architecture, which involves breaking down a monolithic system into smaller, independent components. But while microservices offer many benefits, they also introduce new challenges and trade-offs.&lt;/p&gt;

&lt;p&gt;On the one hand, microservices allow us to develop, test, and deploy individual components independently, which can lead to faster time-to-market and greater agility. They also enable us to scale specific components of the system as needed, rather than having to scale the entire system at once. This can be particularly useful in systems that have varying levels of traffic or usage patterns, as it allows us to allocate resources more efficiently.&lt;/p&gt;

&lt;p&gt;For example, consider an e-commerce platform that experiences a surge in traffic during holiday seasons. With a monolithic architecture, the entire system would need to be scaled up to handle the increased traffic, which can be costly and inefficient. But with a microservices architecture, the platform can scale up only the components that are experiencing high traffic, such as the order processing and payment gateways, while leaving other components, such as the product catalog and customer management, at their normal scale.&lt;/p&gt;

&lt;p&gt;On the other hand, microservices can lead to increased complexity and abstraction. As we break down the system into smaller components, we need to define interfaces and APIs that allow them to communicate with each other. This can lead to a proliferation of interfaces and APIs, which can be difficult to manage and maintain. Additionally, the increased number of components can make it harder to understand the overall system architecture and behavior, which can lead to difficulties in debugging and troubleshooting.&lt;/p&gt;

&lt;p&gt;One of the key challenges of microservices is "interface drift," where the interfaces between different components become increasingly complex and difficult to manage over time. As new features and requirements are added to the system, the interfaces between components must be updated to accommodate these changes, which can lead to a gradual degradation of the system's overall modularity and coherence.&lt;/p&gt;

&lt;p&gt;For example, consider a system that has a component responsible for processing payments, and another component responsible for managing customer information. Initially, the interface between these two components may be simple, with only a few API calls to retrieve customer information and process payments. But over time, as new features are added, such as support for multiple payment methods and customer loyalty programs, the interface between the two components may become increasingly complex, with many more API calls and data exchanges.&lt;/p&gt;

&lt;p&gt;Another challenge is "abstraction leakage," where the abstractions that are intended to simplify the system begin to leak into the implementation details of the individual components. This can lead to a situation where the components become increasingly tightly coupled, despite the best efforts to keep them modular and independent.&lt;/p&gt;

&lt;p&gt;For example, consider a system that has a component responsible for managing customer information, and another component responsible for processing orders. Initially, the two components may be designed to be independent, with their own databases and APIs. But over time, as the system evolves, the components may begin to share data and functionality, leading to a situation where they are tightly coupled and difficult to maintain.&lt;/p&gt;

&lt;p&gt;So how can we balance the benefits of microservices with the challenges of abstraction and complexity? Here are a few strategies that can help:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Keep it simple&lt;/strong&gt;: While it's tempting to create complex interfaces and APIs, it's often better to keep things simple and focused on the essential functionality of each component. This can help to reduce the risk of interface drift and abstraction leakage.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Use APIs and interfaces judiciously&lt;/strong&gt;: Only define interfaces and APIs that are necessary for the system to function, and avoid creating unnecessary complexity. This can help to reduce the number of interfaces and APIs that need to be managed and maintained.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Monitor and maintain&lt;/strong&gt;: Regularly monitor the system's interfaces and APIs, and make adjustments as needed to prevent interface drift and abstraction leakage. This can help to ensure that the system remains modular and maintainable over time.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Prioritize modularity&lt;/strong&gt;: While it's tempting to sacrifice modularity for the sake of simplicity or expediency, it's often better to prioritize modularity and take the time to design and implement components that are truly independent and loosely coupled. This can help to ensure that the system remains scalable and maintainable over time.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Use design patterns and principles&lt;/strong&gt;: Use established design patterns and principles, such as &lt;a href="https://en.wikipedia.org/wiki/Service-oriented_architecture" rel="noopener noreferrer"&gt;service-oriented architecture&lt;/a&gt; (SOA) and &lt;a href="https://en.wikipedia.org/wiki/Domain-driven_design" rel="noopener noreferrer"&gt;domain-driven design&lt;/a&gt; (DDD), to guide the design and implementation of microservices. These patterns and principles can help to ensure that the system is modular, scalable, and maintainable.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Test and validate&lt;/strong&gt;: Thoroughly test and validate the system's interfaces and APIs to ensure that they are working correctly and as intended. This can help to catch errors and defects early, and prevent them from becoming major issues later on.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By being mindful of the challenges and trade-offs of microservices, and by using these strategies to balance abstraction and complexity, we can create systems that are more scalable, flexible, and resilient – and that ultimately deliver more value to our users.&lt;/p&gt;

&lt;p&gt;In addition to the strategies mentioned above, there are several other best practices that can help to mitigate the challenges of microservices. These include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Using a service discovery mechanism&lt;/strong&gt;: A service discovery mechanism, such as &lt;a href="https://etcd.io/" rel="noopener noreferrer"&gt;etcd&lt;/a&gt; or &lt;a href="https://zookeeper.apache.org/" rel="noopener noreferrer"&gt;ZooKeeper&lt;/a&gt;, can help to manage the complexity of microservices by providing a centralized registry of available services and their instances.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Implementing circuit breakers&lt;/strong&gt;: Circuit breakers, such as &lt;a href="https://github.com/Netflix/Hystrix" rel="noopener noreferrer"&gt;Hystrix&lt;/a&gt; or &lt;a href="https://resilience4j.readme.io/docs/getting-started" rel="noopener noreferrer"&gt;Resilience4j&lt;/a&gt;, can help to prevent cascading failures in micro services by detecting when a service is not responding and preventing further requests from being sent to it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Using load balancing and routing&lt;/strong&gt;: Load balancing and routing, such as &lt;a href="https://nginx.org/en/index.html" rel="noopener noreferrer"&gt;NGINX&lt;/a&gt; or &lt;a href="https://www.haproxy.org/" rel="noopener noreferrer"&gt;HAProxy&lt;/a&gt;, can help to distribute traffic across multiple instances of a micro service, improving scalability and availability.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Monitoring and logging&lt;/strong&gt;: Monitoring and logging, such as &lt;a href="https://prometheus.io/" rel="noopener noreferrer"&gt;Prometheus&lt;/a&gt; or &lt;a href="https://www.elastic.co/elastic-stack" rel="noopener noreferrer"&gt;ELK&lt;/a&gt;, can help to provide visibility into the performance and behaviour of micro services, making it easier to identify and troubleshoot issues.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Using containerization&lt;/strong&gt;: Containerization, such as &lt;a href="https://www.docker.com/" rel="noopener noreferrer"&gt;Docker&lt;/a&gt; or &lt;a href="https://kubernetes.io/" rel="noopener noreferrer"&gt;Kubernetes&lt;/a&gt;, can help to simplify the deployment and management of microservices by providing a lightweight and portable way to package and run applications.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By following these best practices and using the strategies mentioned above, we can create microservices that are more scalable, flexible, and resilient – and that ultimately deliver more value to our users.&lt;/p&gt;

&lt;p&gt;Microservices are a powerful tool for building scalable, flexible, and resilient systems. However, they also introduce new challenges and trade-offs, such as increased complexity and abstraction. By being mindful of these challenges and using the strategies and best practices mentioned above, we can create microservices that deliver more value to our users and help to drive business success.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
