<?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: Isaac Hagoel</title>
    <description>The latest articles on DEV Community by Isaac Hagoel (@isaachagoel).</description>
    <link>https://dev.to/isaachagoel</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%2F329991%2F11cf0248-ec4f-4417-856f-ba9b74d7a5a6.png</url>
      <title>DEV Community: Isaac Hagoel</title>
      <link>https://dev.to/isaachagoel</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/isaachagoel"/>
    <language>en</language>
    <item>
      <title>The Best AI Articles Dev.to Won’t Show You</title>
      <dc:creator>Isaac Hagoel</dc:creator>
      <pubDate>Tue, 18 Nov 2025 02:19:31 +0000</pubDate>
      <link>https://dev.to/isaachagoel/the-best-ai-articles-devto-wont-show-you-iph</link>
      <guid>https://dev.to/isaachagoel/the-best-ai-articles-devto-wont-show-you-iph</guid>
      <description>&lt;p&gt;Dev.to's feed is broken. It never shows me posts I actually want to read. The search engine doesn’t help either.&lt;br&gt;&lt;br&gt;
I had a feeling there’s still good, advanced level content being published, buried under the clickbait and slop. So I made my own tool to find these hidden gems.&lt;br&gt;&lt;br&gt;
I use it daily to find recently published (last 24 hours), original, and insightful posts about AI and success. I usually end up with a couple of solid reads every day.&lt;/p&gt;

&lt;p&gt;I’ll share the ones I like best here and update this page as I discover new ones.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Bookmark this page if you want to stay up to speed&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclaimer: I don’t endorse these posts. The opinions expressed belong to their authors. I just find them thought-provoking and worth reading.&lt;/em&gt;&lt;br&gt;&lt;br&gt;
Enjoy.&lt;/p&gt;

&lt;h2&gt;
  
  
  2025/11/28-29
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://dev.to/nodefiend/trust-the-server-not-the-llm-a-deterministic-approach-to-llm-accuracy-20ag"&gt;Trust the Server, Not the LLM: A Deterministic Approach to LLM Accuracy&lt;/a&gt; by &lt;a class="mentioned-user" href="https://dev.to/nodefiend"&gt;@nodefiend&lt;/a&gt;
 Useful techniques for LLM output quality controls by grounding, verification and reduced degrees of freedom.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/jensen_king_9fa3ffe58c0a1/deepseekmath-v2-how-far-are-we-from-true-agi-when-ai-learns-to-self-negate-5h5a"&gt;DeepSeekMath-V2: How far are we from true AGI when AI learns to self-negate?&lt;/a&gt; by &lt;a class="mentioned-user" href="https://dev.to/jensen_king_9fa3ffe58c0a1"&gt;@jensen_king_9fa3ffe58c0a1&lt;/a&gt;
Deepseek keeps exploring interesting ideas. This time around around two levels of supervision in order to force a model to do a good job when thinking through a problem.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  2025/11/23-27
&lt;/h2&gt;

&lt;p&gt;I did check daily, but all I found was AI slop :(&lt;/p&gt;

&lt;h2&gt;
  
  
  2025/11/20-22
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://dev.to/camel-ai/brainwash-your-agent-how-we-keep-the-memory-clean-24nn"&gt;Brainwash Your Agent: How We Keep The Memory Clean&lt;/a&gt; by &lt;a class="mentioned-user" href="https://dev.to/camel-ai"&gt;@camel-ai&lt;/a&gt;
This one is a real gem. Very practical and well written (but doesn't feel AI written) guide on context compaction, by people who are at the front of the field. Full of useful links. &lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  2025/11/19-20
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://dev.to/googlecloud/the-lumberjack-paradox-from-theory-to-practice-2lb5"&gt;The lumberjack paradox: From theory to practice&lt;/a&gt; by &lt;a class="mentioned-user" href="https://dev.to/sigje"&gt;@sigje&lt;/a&gt;
This post gave me some serious food for thought. We let AI read out code, documentation and specifically code samples "as is" and that's actually bad. "The lumberjack paradox" and other concepts the author mentions are also cool!
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://dev.to/brielov/i-needed-date-math-in-formulas-so-i-built-a-compiler-and-learned-a-lot-104m"&gt;I Needed Date Math in Formulas, So I Built a Compiler (and Learned a Lot)&lt;/a&gt; by &lt;a class="mentioned-user" href="https://dev.to/brielov"&gt;@brielov&lt;/a&gt;&lt;br&gt;&lt;br&gt;
This one is a realistic take about building something non-trivial for production with AI, but the most interesting and educational part is about how he went about designing the expression parser if you're into this kind of stuff.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://dev.to/rikinptl/explainable-causal-reinforcement-learning-for-smart-agriculture-microgrid-orchestration-with-5567"&gt;Explainable Causal Reinforcement Learning for smart agriculture microgrid orchestration with ethical auditability baked in&lt;/a&gt; by &lt;a class="mentioned-user" href="https://dev.to/rikinptl"&gt;@rikinptl&lt;/a&gt;&lt;br&gt;&lt;br&gt;
This one goes deep into ML but even for engineers such as myself, there is a lot to chew on. It's anchor in a real life system and provides an eye opening account of the different tradeoffs, challenges and solutions including many code snippets.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  2025/11/18-19
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://dev.to/alifar/deepseek-ocr-in-automation-pipelines-practical-engineering-insights-and-integration-patterns-3g4a"&gt;DeepSeek OCR in Automation Pipelines: Practical Engineering Insights and Integration Patterns&lt;/a&gt; by &lt;a class="mentioned-user" href="https://dev.to/alifar"&gt;@alifar&lt;/a&gt;&lt;br&gt;&lt;br&gt;
When Deepseek OCR's paper landed, I was wondering what the hype is all about and what using it in real world scenarios would look like. This post takes a peek at that. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://dev.to/michaelsolati/im-getting-serious-deja-vu-but-this-time-its-different-17f4"&gt;I'm Getting Serious Déjà Vu... But This Time It's Different&lt;/a&gt; by &lt;a class="mentioned-user" href="https://dev.to/michaelsolati"&gt;@michaelsolati&lt;/a&gt; &lt;br&gt;
Nice opinion piece about how AI affects the software development job market.  &lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  2025/11/17–18
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://dev.to/lofcz/the-shift-towards-agentic-ai-what-it-means-for-developers-4a4o"&gt;The Shift Towards Agentic AI: What It Means for Developers&lt;/a&gt; by lofcz&lt;br&gt;&lt;br&gt;
While the title comes off a bit generic, the article itself has genuinely sharp insights and correctly calls out common pitfalls (and solutions) when implementing agents.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://dev.to/siddhantkcode/context-engineering-the-critical-infrastructure-challenge-in-production-llm-systems-4id0"&gt;Context Engineering: The Critical Infrastructure Challenge in Production LLM Systems&lt;/a&gt; by siddhantkcode&lt;br&gt;&lt;br&gt;
This one goes deep on advanced ways to keep context lean and mean. It doesn’t give every detail, but it &lt;em&gt;does&lt;/em&gt; link full access to the code, which is even better 😄&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://dev.to/mechero22/the-vibe-coding-trap-why-conversational-ai-makes-developers-slower-1o7i"&gt;The Vibe Coding Trap: Why Conversational AI Makes Developers Slower&lt;/a&gt; by mechero22&lt;br&gt;&lt;br&gt;
This one’s a bit spicy, and I’m not fully onboard with all the claims it makes. But even if it rubs you the wrong way, it will definitely give you something to chew on.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
    </item>
    <item>
      <title>Sushify - A New Free &amp; Essential Tool For AI App Developers</title>
      <dc:creator>Isaac Hagoel</dc:creator>
      <pubDate>Sat, 06 Sep 2025 12:12:23 +0000</pubDate>
      <link>https://dev.to/isaachagoel/sushify-a-new-free-essential-tool-for-ai-app-developers-45n6</link>
      <guid>https://dev.to/isaachagoel/sushify-a-new-free-essential-tool-for-ai-app-developers-45n6</guid>
      <description>&lt;p&gt;I recently (well, today 😊) released &lt;a href="https://github.com/pragmaticfish/sushify" rel="noopener noreferrer"&gt;Sushify&lt;/a&gt;, an open-source dev tool that helps test apps with complex LLM integrations by surfacing prompt* issues early. I write prompt* because, as you already know if you’ve worked on such apps, the prompt itself is just a small part of the broader context management required to make production-grade AI apps work well. &lt;a href="https://github.com/pragmaticfish/sushify" rel="noopener noreferrer"&gt;Sushify&lt;/a&gt; uncovers issues in everything that gets passed into the LLM (including tools, output schemas, history, etc.).&lt;/p&gt;

&lt;p&gt;In other words, it helps you turn your prompt salad into precision-cut sushi 👌🏻.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It’s still early days, and I’m looking for feedback and contributions from early adopters.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This post is a walkthrough of how I got from initial frustration to publishing a tool others can now use.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Production apps that utilize LLMs often express a lot of their logic in free text—because that’s what LLMs understand.&lt;/p&gt;

&lt;p&gt;Prompts are usually composed of static snippets and/or templates that get stitched together at runtime (sometimes using loops or conditional logic) and shared across different agents or workflows. On top of that, we pass in tools: each with a top-level description, parameters with descriptions, and usually prompt fragments that refer to the tool’s input/output format. The same goes for output schemas (a.k.a. structured outputs).&lt;/p&gt;

&lt;p&gt;If one thing changes, say a tool’s output format, but there’s still a lingering reference to the old version anywhere in the prompt, the LLM can get confused and start misbehaving.&lt;/p&gt;

&lt;p&gt;To make things worse, instructions to the LLM can easily end up too vague, too restrictive, or even contradictory (sometimes contradicting your &lt;em&gt;past self&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;As a result, the LLM starts ignoring instructions or behaving unpredictably. The typical response is to make things worse by piling on even more free-text instructions as a patch.&lt;/p&gt;

&lt;p&gt;I ran into this struggle repeatedly, both in production-grade AI apps and even in small side projects. One day I stepped back, realized how absurd the whole situation was, and decided to do something about it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Initial Instinct - Static Analysis
&lt;/h2&gt;

&lt;p&gt;My first thought was: for code we have linters and compilers, so why not do the same for the inputs that go into an LLM?&lt;/p&gt;

&lt;p&gt;I wanted to support at least Python and TypeScript and provide source maps that would pinpoint every issue back to its exact origin.&lt;/p&gt;

&lt;p&gt;I spent a few weeks trying different approaches. The idea was to locate the LLM call in the code and build a DAG (Directed Acyclic Graph) tracing all dependencies through the codebase, ultimately reconstructing the full prompt* for analysis.&lt;/p&gt;

&lt;p&gt;I tested this with real side projects I had built beforehand, but it wasn’t reliable enough. Some data injected into prompts is only known at runtime and would require mocking (e.g., RAG-retrieved docs, tool outputs, API call results). Prompt composition could also be deceptively tricky - even simple ternary expressions were hard to untangle.&lt;/p&gt;

&lt;p&gt;No matter how sophisticated I made it, it never felt “good enough.” On top of that, it was expensive. I burned through over $100 just trying to generate dependency graphs, not even analyzing prompts yet. Eventually, I had to step back and rethink the approach.&lt;/p&gt;




&lt;h2&gt;
  
  
  Second Attempt - Runtime Tracking
&lt;/h2&gt;

&lt;p&gt;If static analysis wasn’t cutting it, the alternative was to track LLM calls at runtime.&lt;/p&gt;

&lt;p&gt;This had some clear advantages: no guesswork or mocking - the tool would see exactly what the LLM sees. Sure, to analyze every possible permutation of the prompt*, we’d need to actually execute those code paths, but is it really that hard for a dev to make the relevant calls?&lt;/p&gt;

&lt;p&gt;Another big plus: we’d see the LLM responses too. That meant cross-referencing potential input issues with actual model behavior, capturing follow-up messages, history, tool responses, and even context-compaction bugs.&lt;/p&gt;

&lt;p&gt;It made a lot of sense but there was still plenty to figure out.&lt;/p&gt;




&lt;h2&gt;
  
  
  Iterations
&lt;/h2&gt;

&lt;p&gt;I won’t bore you with every failed experiment, but here’s the gist.&lt;/p&gt;

&lt;p&gt;First, I built a POC using an SDK: the monitored app had to call this SDK and pass in the same payload it sent to the LLM (or wrap every LLM provider SDK with it).&lt;/p&gt;

&lt;p&gt;This quickly felt wrong: too much friction, too error-prone (e.g., Zod schemas not being transformed into JSON schemas), and too restrictive. I wanted plug-and-play simplicity: something I could drop into any app with minimal effort.&lt;/p&gt;

&lt;p&gt;That’s when I landed on using a proxy. Instead of requiring the app to wire anything manually, the proxy would wrap the app and intercept calls to the LLM - capturing the exact same request and response, reliably and transparently.&lt;/p&gt;

&lt;p&gt;And of course, it had to support Docker, since nearly every production app I work on is containerized.&lt;/p&gt;




&lt;h2&gt;
  
  
  Sushify
&lt;/h2&gt;

&lt;p&gt;After all that exploration, I ended up with &lt;strong&gt;Sushify&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It’s still barebones, but already super useful. It helped me uncover issues in projects I thought were fine. It makes debugging prompt-related problems ridiculously easy.&lt;/p&gt;

&lt;p&gt;Even though there’s plenty of room for growth, I’m confident many developers can get serious value out of it today.&lt;/p&gt;

&lt;p&gt;Check out the &lt;a href="https://github.com/pragmaticfish/sushify" rel="noopener noreferrer"&gt;GitHub repo&lt;/a&gt; - it has everything you need to get started in a few minutes, plus a quick demo and screenshots. Oh, and feel free to leave a ⭐️ while you’re there 😉.&lt;/p&gt;

&lt;p&gt;Would love to hear your thoughts or questions in the comments!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Stop Building AI Agents! Start Building Real AI Software Instead</title>
      <dc:creator>Isaac Hagoel</dc:creator>
      <pubDate>Mon, 18 Aug 2025 07:51:10 +0000</pubDate>
      <link>https://dev.to/isaachagoel/stop-building-ai-agents-start-building-real-ai-software-instead-4ppd</link>
      <guid>https://dev.to/isaachagoel/stop-building-ai-agents-start-building-real-ai-software-instead-4ppd</guid>
      <description>&lt;p&gt;Every true revolution has its detours. For AI, the promise is real, something fundamental has shifted. But as the latest &lt;a href="https://www.gartner.com/en/articles/hype-cycle-for-artificial-intelligence" rel="noopener noreferrer"&gt;Gartner AI Hype Cycle&lt;/a&gt; shows, our first big bet, the age of autonomous AI agents, turned out to be the wrong turn, at least for now.&lt;/p&gt;

&lt;p&gt;Why did we believe so strongly? The vision was intoxicating: describe a goal, hand your agent some tools, and let it handle the mess. No more manual wiring, no more business logic - the agent would figure it out, freeing us from tedious problem solving. For a while, it felt within reach; releases like o1, Claude-3.5-Sonnet, and GPT-4.1 were genuine leaps over what came before, unlocking new, reliable real-world use-cases. But then, momentum started to stall. Each new model (o3, GPT-4.5, Grok4, GPT-5) was hyped as the breakthrough, the moment when agents would finally work. Benchmarks inched higher, expectations soared.&lt;/p&gt;

&lt;p&gt;But real-world builders know: the fundamentals barely changed. Agents still hallucinate, lose context, and require endless handholding. When long anticipated GPT-5 landed and the leap still didn’t arrive, the industry finally had to admit: the breakthrough wasn’t coming. Now, we’re firmly in the &lt;a href="https://www.economist.com/business/2025/05/21/welcome-to-the-ai-trough-of-disillusionment" rel="noopener noreferrer"&gt;“trough of disillusionment”&lt;/a&gt; - the part of the cycle where the dreams get recalibrated and real progress starts.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwz5vhdvj3hahu99j7yet.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%2Fwz5vhdvj3hahu99j7yet.png" alt="image The Gartner Hype Cycle" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Let me put it bluntly: if you still believe fully autonomous agents are here, show me a single agent working well in production outside of coding copilots. And even those coding agents are not really autonomous. They rely on constant back-and-forth with the user, who steers them and fixes their mistakes.&lt;/p&gt;

&lt;p&gt;The revolution isn’t cancelled. It’s just moved. The way forward is not autonomy at all costs, but tight, explicit integrations where LLMs are used as powerful, controlled components, not left to run the show. The slope of enlightenment is right in front of us, but it starts with dropping the agent fantasy.&lt;/p&gt;

&lt;p&gt;I’ve spent the past eight months in the trenches, shipping AI features in production, fighting with agent frameworks, and watching the same problems crop up again and again. Here’s the hard truth: agents sound good on conference slides, but in the real world they break, drift, and stall unless you babysit every step.&lt;/p&gt;

&lt;p&gt;So what’s the alternative? If you want reliability, iteration speed, and real business value, it’s time to treat the LLM as plumbing, not the pilot. &lt;/p&gt;

&lt;h2&gt;
  
  
  The Allure of Agents &amp;amp; the Hype Machine
&lt;/h2&gt;

&lt;p&gt;The agent gold rush wasn’t driven by developers alone. Three main actors shaped the frenzy, each with different bets and incentives. Framework makers, racing to build the glue and protocols for “autonomous” orchestration, were gambling on foundation model progress unlocking true autonomy. Hardware vendors and cloud platforms, from GPU makers to AI infrastructure startups, stood to profit from any paradigm that made AI workloads heavier and more ubiquitous. And then, of course, the foundation model companies themselves: OpenAI, Anthropic, Google, xAI, who fueled the optimism but quietly hedged their own bets, as we’ll see later.&lt;/p&gt;

&lt;p&gt;This ecosystem of overlapping interests turned the agent vision from a technical hypothesis into an industry narrative. That narrative was powerful, intoxicating, and, for a time, felt inevitable.&lt;/p&gt;

&lt;p&gt;The frameworks themselves grew more ambitious with each passing quarter. &lt;a href="https://www.crewai.com/" rel="noopener noreferrer"&gt;CrewAI&lt;/a&gt;, &lt;a href="https://www.swarms.ai/" rel="noopener noreferrer"&gt;Swarms&lt;/a&gt; &lt;a href="https://www.langchain.com/langgraph" rel="noopener noreferrer"&gt;LangGraph&lt;/a&gt;, and a host of other libraries and protocols sketched out a future where countless agents could collaborate in harmony; delegating, calling each other, and weaving together complex workflows. Vendors rolled out “research agents,” “autonomous computer use agents,” and more, hoping to lead the new stack. Demo videos showed swarms of agents reasoning their way through multi-step challenges, and slide decks promised a world where “set it and forget it” would finally apply to enterprise software. The sheer volume of articles, guides, and open source projects made it feel like this new order was just around the corner. In reality, none of these vendor-driven agents took off in a meaningful way. The userbase rejected them. &lt;/p&gt;

&lt;h2&gt;
  
  
  Piston Engines, Paradigm Shifts, Benchmarks and AI Models
&lt;/h2&gt;

&lt;p&gt;The story of today’s large language models closely parallels the golden age of piston engines in aviation. For decades, engineers genuinely believed that with bigger and more powerful piston engines and ever-better propeller designs, airplanes would keep getting faster - maybe even break the sound barrier. At first, every increment of horsepower seemed to open up new possibilities, but soon each leap delivered less and less. Eventually, the fundamental limits of the piston engine and propeller system became clear: pushing further would take a new paradigm. As historian Edward Constant puts it, “huge increases in engine horsepower were yielding smaller and smaller increases in speed” (&lt;a href="https://www.grahamhoyland.com/can-a-spitfire-break-the-sound-barrier" rel="noopener noreferrer"&gt;grahamhoyland.com&lt;/a&gt;), and as late as the 1930s, many in the field still believed a breakthrough was just ahead (&lt;a href="https://www.britannica.com/technology/military-aircraft/The-jet-age" rel="noopener noreferrer"&gt;Britannica&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Yet, piston-engine planes were world-changing in their era - no one would call them a failure. They delivered most of what aviation needed for decades, just as LLMs now deliver astonishing, practical results across industries. We’re now at a similar point with LLMs. The transformer paradigm produced incredible breakthroughs, but simply scaling these models (even with reasoning) isn’t unlocking robust, general-purpose autonomy. &lt;/p&gt;

&lt;h3&gt;
  
  
  But Benchmarks...
&lt;/h3&gt;

&lt;p&gt;Recent releases do bring new benchmark highs, but the leap forward for real-world use just isn’t materializing. Much of this is because benchmarks measure “in-distribution” skills: tasks that are close to what the models have already seen. LLMs shine here. But step out of that comfort zone toward unfamiliar, more complex, or genuinely multi-step work and the cracks show: hallucinations, brittle context, unreliable reasoning.&lt;/p&gt;

&lt;p&gt;Researchers also noticed this benchmark disconnect. As &lt;a href="https://arxiv.org/html/2412.03597v1" rel="noopener noreferrer"&gt;this paper&lt;/a&gt; puts it: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The pursuit of leaderboard rankings in Large Language Models (LLMs) has created a fundamental paradox: models excel at standardized tests while failing to demonstrate genuine language understanding and adaptability. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Another &lt;a href="https://arxiv.org/html/2502.14318v1" rel="noopener noreferrer"&gt;recent paper&lt;/a&gt; points to systemic limitations in the current benchmarking paradigm. &lt;/p&gt;




&lt;p&gt;None of this diminishes what we have in front of us. The improvements we still see in speed, cost and marginal capabilities are real and worth celebrating. But the nature of our tools is now clear and accepting their boundaries is what will finally allow us to build the next generation of AI software, rather than waiting for paradigm shifts that might take many years to arrive.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Agents Fail in Practice
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What is an “AI agent”?&lt;/strong&gt; In the context of LLMs, an agent is a system that uses an LLM to autonomously plan and execute multi-step tasks: breaking goals into actions, deciding what to do next, choosing tools or APIs, and iterating—ideally without human intervention (&lt;a href="https://www.anthropic.com/engineering/building-effective-agents" rel="noopener noreferrer"&gt;Anthropic&lt;/a&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Why do they fail in the real world?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Loss of control and premature exit:&lt;/strong&gt; Agents often “think” they’ve finished before all parts of a task are truly complete. They may exit prematurely, get stuck in loops, or miss obvious next steps - especially as tasks get more complex, long or open-ended.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strict complexity limits:&lt;/strong&gt; As the number of instructions, tools, or task history grows, performance and reliability degrade sharply.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hallucinated actions and broken chains:&lt;/strong&gt; Agents frequently invent tool calls, take invalid actions, or misinterpret what’s needed—resulting in broken workflows or failure to deliver usable results.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cascading errors:&lt;/strong&gt; A mistake in one step can cause a chain reaction, with no robust recovery. The system can veer off course, miss the goal, or require manual reset.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Poor context management:&lt;/strong&gt; Agents can’t reliably hold or use all relevant context as tasks grow longer, causing confusion, forgotten requirements, or inconsistent decisions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Opacity and lack of debuggability:&lt;/strong&gt; It’s often unclear why an agent did what it did. Debugging and reproducing failures is notoriously hard.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Human-in-the-loop dependence:&lt;/strong&gt; For any non-trivial task, a human must step in to guide, correct, or “babysit” the agent. True autonomy almost never survives outside of narrow, simple demos.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;And about those demos:&lt;/em&gt; Most agent demos are meticulously iterated until they perform a single showcase scenario perfectly. It’s easy to make a video of an agent executing a specific, well-groomed task. What’s hard and still unsolved is getting robust, reliable performance in the messy, unpredictable real world.&lt;/p&gt;

&lt;h2&gt;
  
  
  Remembering What Software Is Actually About
&lt;/h2&gt;

&lt;p&gt;Software engineering is about taming complexity by breaking big problems into smaller, understandable parts. Each part is explicitly defined, with clear interfaces and predictable outcomes. We use modularization and encapsulation to create boundaries, making it possible to test, reason about, and improve each part independently.&lt;/p&gt;

&lt;p&gt;Good software makes intervention points clear. You can trace data as it flows through the system, observe where decisions are made, and know exactly where to apply a fix or add new logic. Branching paths and possible system states are explicit—not left to be inferred from opaque behavior.&lt;/p&gt;

&lt;p&gt;With strong boundaries and transparency, you maintain control as your system grows. This discipline is what makes it possible to build reliable, scalable software - no matter how ambitious the project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Workflows, APIs, and the Real Path Forward
&lt;/h2&gt;

&lt;p&gt;If you work on AI applications, you’ve heard "agentic workflows" described as a lesser, “un-evolved” version of agents - maybe even a stopgap until agents get smart enough. The Anthropic team and others have framed agents as the natural next step, capable of solving more open-ended or complex tasks than traditional, stepwise workflows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But that view is backwards&lt;/strong&gt;. In real-world scenarios, workflows can be dramatically more powerful, reliable, and extensible than agents. A well-designed workflow gives you granular control and intervention points at every boundary. Each state and branch is mapped, explicit, and testable. Workflows enable visibility, debuggability, and precise engineering at scale.&lt;/p&gt;

&lt;p&gt;This pattern isn’t new. For decades, software has relied on orchestrating external APIs and services. From the application’s perspective, these are “magic” - but they always speak in contracts, return well formed responses, and fit precisely into the broader workflow. Why not treat LLMs in exactly the same way? Give the model a narrow, well-scoped job, validate the output, and plug it into your workflow like any other high-powered component.&lt;/p&gt;

&lt;p&gt;We’re lucky that API model makers like OpenAI, Anthropic, and Google have quietly given us the tools we need for this approach. Their APIs provide ever improving structured output modes with type and schema enforcement, temperature and randomness controls, and even regex based constraints on outputs (&lt;a href="https://platform.openai.com/docs/guides/structured-outputs?api-mode=responses&amp;amp;lang=javascript" rel="noopener noreferrer"&gt;OpenAI Structured Outputs&lt;/a&gt;). These features let us treat LLMs as reliable, predictable, and tightly controlled components - like any other production API.&lt;/p&gt;

&lt;p&gt;Agents (single or a group of them talking to each other however the please) promise more adaptability but fail to deliver. They sacrifice the very things that make production software good. &lt;br&gt;
&lt;strong&gt;Our goal shouldn't be about chasing autonomy for its own sake but about delivering high quality, never possible before apps and features to our users&lt;/strong&gt; (who &lt;a href="https://x.com/andrewchen/status/1952577132586213705" rel="noopener noreferrer"&gt;couldn't care less about AI&lt;/a&gt; btw).&lt;/p&gt;

&lt;h2&gt;
  
  
  Workflow vs. Agent: The Flight and Hotel Booking Test
&lt;/h2&gt;

&lt;p&gt;Let’s use the familiar (though contrived) “Book a flight and hotel” scenario—often showcased as the ultimate test for agent frameworks. The examples below are simplified for educational purposes. &lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Agent Approach&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Setup:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Tools provided:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;search_flights(origin, destination, date, arrival_time)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;book_flight(flight_id, passenger_info)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;search_hotels(city, checkin_date, checkout_date, near)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;book_hotel(hotel_id, guest_info)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;send_email(recipient, content)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;say_to_user(message)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt; planning tools like &lt;code&gt;add_todo&lt;/code&gt;, &lt;code&gt;update_todo&lt;/code&gt;, &lt;code&gt;read_todo&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;Prompt:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;
A long, detailed instruction set describing the user goal, each tool and its parameters, usage rules, and task-specific edge cases. Typically tousands of tokens just for context.&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;In theory:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
The agent receives the user goal (“Book me a flight to Berlin on May 22nd, arriving before noon, and a hotel for two nights near the conference venue”), then plans and executes—deciding which tools to call, in what order, and how to handle ambiguity, branching or errors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What actually happens:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The agent may exit after booking a flight, skipping the hotel, or vice versa.&lt;/li&gt;
&lt;li&gt;It often ignores critical constraints (“arrive before noon”), or invents data not in the API responses.&lt;/li&gt;
&lt;li&gt;It can take destructive actions like booking the wrong flight.&lt;/li&gt;
&lt;li&gt;With each added tool or requirement, prompt and tool complexity grows, and error rates rise.&lt;/li&gt;
&lt;li&gt;Debugging or extending the workflow means rewriting prompts, retraining, or adding brittle heuristics or begging the agent to do the right thing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The A-ha Moment
&lt;/h3&gt;

&lt;p&gt;But wait, when you look closely, booking a trip, like most business tasks, doesn’t actually require AGI-level flexibility or deep autonomy. It’s a highly structured problem, following a predictable set of steps towards a successful completion. The apparent complexity comes from edge cases and details, not from open-ended reasoning. When you break it down, almost every aspect can be handled with clear logic, explicit checks, and a series of well-defined handoffs. The “magic” is in the composition, and the need for intelligence can be limited within clear boundaries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Workflow/LLM Integration Approach
&lt;/h2&gt;

&lt;p&gt;This approach is about code-first orchestration. LLMs are used only for well-defined, bounded tasks and for focused decision making, never for orchestrating.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example Workflow
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Parse user input&lt;/strong&gt; (only if using a chat interface, which is often unnecessary) 

&lt;ul&gt;
&lt;li&gt;Code calls LLM with a schema-enforced prompt:
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;result&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;parsedParams&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;destination&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Berlin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;arrival_date&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2024-05-22&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;arrival_time&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;09:30&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;nights&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;venue_address&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Berlin Congress Center&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="c1"&gt;// or (via Zod union type or Pydantic)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;result&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;missing_info&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;feedbackToUser&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;What city should I book the hotel in?&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;ul&gt;
&lt;li&gt;Vendor side schema validation (a.k.a Open AI's structured outputs) guarantees outputs are always structured and complete.&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Branch: Request missing info or continue&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Code inspects the response, if we got "missing info" - prompt the user with the feedback, when the user responds feed the full exchange into the first LLM again. If "success", move to search.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prepare search parameters &amp;amp; call APIs&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Code builds params for booking APIs (both for exact matches and for alternate/flexible options if needed).&lt;/li&gt;
&lt;li&gt;Code, not the LLM, calls these APIs in parallel and collects results. No hallucinations possible&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LLM-powered ranking (tightly scoped)&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Code sends the original query and API results to an LLM.&lt;/li&gt;
&lt;li&gt;LLM returns a sorted list of candidate options, with justifications, in a strict schema (notice that the output only contains ids, even though we provided the full details as input - no need for the LLM to repeat information we already have):
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;        &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;LH1234&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rank&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;explanation&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Arrives before noon, direct flight, good price.&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;LH5678&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rank&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;explanation&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Slightly later arrival, lower price.&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;ul&gt;
&lt;li&gt;IDs are validated against the real API results. Again - no room for hallucinated data.&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Present the best options to the user&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Present the results to the user&lt;/li&gt;
&lt;li&gt;Branch - the user is unhappy - provides additional requirements - we go back to the first LLM with the full exchange&lt;/li&gt;
&lt;li&gt;The user selected one option and now we need to Book the flight - this can be done by code now or later&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Booking a hotel&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Repeat a similar process but use the LLM to smartly select a city, a location in the city and create search filters based on input from the user or information we have about the user (e.g. past preferences) &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;strong&gt;Key Points:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;LLMs do not orchestrate—they handle bounded parsing or ranking jobs, always with explicit, code-enforced contracts.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;All critical state and business logic stays in code—clear, testable, and maintainable.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Every failure point is observable and recoverable.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Each LLM call can have an extremely detailed, targeted prompt and efficient usage of its context window&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Contrast with agentic frameworks:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Many “agentic workflow” frameworks (like &lt;a href="https://langchain-ai.github.io/langgraphjs/tutorials/workflows/" rel="noopener noreferrer"&gt;LangGraph&lt;/a&gt;) promote chaining LLM calls, connecting nodes in a graph, and using prompt chaining utilities as the main pattern. Even when the LLM isn’t making every decision, the framework subtly encourages LLM centric designs that don't look like good old procedural logic. In other words, most current frameworks push you toward agentic complexity. The truth is that if you follow the code-first pattern above, you may find you need very little extra scaffolding or abstractions these frameworks offer. &lt;/p&gt;

&lt;h2&gt;
  
  
  Caveats &amp;amp; Legitimate Exceptions
&lt;/h2&gt;

&lt;p&gt;This isn’t dogma - the programming model I described above allows tightly-scoped "agentic" loops, retries with feedback etc.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Tightly Scoped Feedback Loops (scoped decision making):&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
When generating artifacts like SQL queries with an LLM, the code executes the query. If it fails, the error (or even successful results) are sent back to the LLM for revision or validation of the queries, with a limited number of retries. The query results can then be handed off to code or another LLM for processing. These loops are bounded, schema-driven, and transparent. They’re pragmatic error recovery, not open-ended “agentic” wandering.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Multi-Step Reasoning Without Agentic Loops:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Some tasks, like analyzing a massive file or gradually tracing logic, seem to call for agents because they can't be done effectively in a single pass. But the new generation of &lt;em&gt;reasoning models&lt;/em&gt; (think o3, Deepseek R1) can often handle such complexity internally, in a single, well-structured prompt. The LLM gets everything it needs up front and processes as much as it wants via multiple reasoning steps, until it is ready to return a single output that your code can validate and act on, no agentic looping required.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Tools/ Function Calling:&lt;/strong&gt;&lt;br&gt;
Yes - In some very complex scenarios you would have to pass tools and let the LLM decide on the exact calls to make before returning with the desired output. It's unlikely these scenarios exist in your app (😉) but - If you can't avoid it, remember to keep the agent in question as small and focused as possible and to minimise the number of tools you give it. &lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Summary:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Don’t avoid feedback loops or multi-step reasoning. Just keep them bounded, schema-driven, and under the control of explicit code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up (and What’s Next)
&lt;/h2&gt;

&lt;p&gt;The AI revolution is real but building robust, production-ready AI software means letting go of the agent fantasy and returning to the fundamentals that made software engineering great. Tightly controlled, code-centric integrations win every time.&lt;/p&gt;

&lt;p&gt;There’s still a missing piece: the right tools, patterns, and principles for this new paradigm barely exist outside of the heads of a few experts. The current ecosystem is immature, and most frameworks still push us toward agentic complexity. Defining and building a real code-first stack and a shared set of “AI software engineering” principles—will be the next challenge for our community.&lt;/p&gt;

&lt;p&gt;I’ll be sharing more practical patterns, tooling ideas, and principles in upcoming posts. If you’re building in this space, let’s connect. Disagree with me? Seen agents work in the wild? Share your stories in the comments or reach out.&lt;/p&gt;




</description>
      <category>ai</category>
      <category>programming</category>
      <category>machinelearning</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Why LLM Memory Still Fails - A Field Guide for Builders</title>
      <dc:creator>Isaac Hagoel</dc:creator>
      <pubDate>Tue, 29 Jul 2025 06:20:57 +0000</pubDate>
      <link>https://dev.to/isaachagoel/why-llm-memory-still-fails-a-field-guide-for-builders-3d78</link>
      <guid>https://dev.to/isaachagoel/why-llm-memory-still-fails-a-field-guide-for-builders-3d78</guid>
      <description>&lt;p&gt;It's an open secret that despite the immense power of Large Language Models, the AI revolution hasn’t swept through every industry and not for lack of trying. We were warned AI would take our jobs and replace every app, but that hasn’t happened. Why?&lt;/p&gt;

&lt;p&gt;Some would say "hallucinations," but let’s be honest - people hallucinate too, and often more than modern LLMs. The real missing piece, the thing standing in the way of the AI tsunami, is &lt;strong&gt;memory&lt;/strong&gt;: the ability to learn, grow, and evolve over time.&lt;/p&gt;

&lt;p&gt;Imagine hiring an AI as a new team member. You wouldn’t expect them to know everything on day one. They’d need to learn the role, get to know the team, understand your business logic, make mistakes, get feedback, and improve. All of that learning happens over time.&lt;/p&gt;

&lt;p&gt;LLMs as they exist today, even when equipped with the best available tools, can’t do any of that. They are stateless and frozen in time. &lt;/p&gt;

&lt;p&gt;This isn’t a theoretical overview - I rolled up my sleeves and tested real systems to see what actually works.&lt;/p&gt;




&lt;h3&gt;
  
  
  Stateless Intelligence
&lt;/h3&gt;

&lt;p&gt;The only way to introduce new information or "teach" an LLM new skills is by providing it with examples, instructions, and all the accumulated relevant information repeatedly with every invocation (prompt). This blob of tokens given to the model is know as "context" and has a clear limit - "context rot": The more you stuff into the prompt, the harder it becomes for the model to separate signal from noise and know what to attend to. This is true even for models with large context windows e.g.1M tokens for GPT4.1 or Gemini 2.5 Pro. We all know this first hand from experience - that moment when you realise you need to start a new chat because the model gets all confused, and it's also &lt;a href="https://research.trychroma.com/context-rot" rel="noopener noreferrer"&gt;backed by research&lt;/a&gt;. In other words, the idea of dumping "all the datas" into the context window fails the test of reality.&lt;/p&gt;

&lt;p&gt;Because of that, &lt;a href="https://x.com/karpathy/status/1937902205765607626" rel="noopener noreferrer"&gt;Context Engineering&lt;/a&gt; is the "make or break" pillar of any sufficiently complex LLM-centered feature/app and the most important skill for engineers building with AI.&lt;/p&gt;

&lt;p&gt;This is why there is a whole industry around "how to get relevant information into the context" (and flush it out or compact it when it becomes less relevant). There is a whole slew of commercial and open source offering, all of which revolve around different flavours of &lt;a href="https://en.wikipedia.org/wiki/Retrieval-augmented_generation" rel="noopener noreferrer"&gt;RAG&lt;/a&gt; (e.g. Agentic RAG, Graph RAG).&lt;/p&gt;

&lt;p&gt;The real confusion begins when these offerings start using the name "memory" for these RAG-based solutions - sometimes &lt;a href="https://langchain-ai.github.io/langgraph/concepts/memory/#memory-types" rel="noopener noreferrer"&gt;going as far as splitting it into categories&lt;/a&gt; like "episodic memory" or "semantic memory". This is smart for marketing but creates  false expectations by analogy. &lt;/p&gt;

&lt;p&gt;Another thing you'd notice if you start playing with these "memory" frameworks/libraries is that they focus on interactions with a single user - the "chatbot" use case we know today but definitely not what a real agent that continuously operates in an "open world" environment (like the AI worker we discussed before) requires.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What About The Memory Feature In ChatGPT?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When OpenAI say they &lt;a href="https://openai.com/index/memory-and-new-controls-for-chatgpt/" rel="noopener noreferrer"&gt;added memory to ChatGPT&lt;/a&gt; what they actually mean is that they gave the model the ability to store a flat list of blobs of textual information about the user, and perform searches on that (using RAG presumably). As before, the scope is a single user and it suffers from all the normal limitations of RAG, which we will discuss next. &lt;/p&gt;




&lt;h3&gt;
  
  
  “Memory” Systems That Aren’t 
&lt;/h3&gt;

&lt;p&gt;I explored most of the major "memory" implementations out there. As I said before, all of them are search tools with the label "memory" slapped on top.&lt;/p&gt;

&lt;p&gt;Most fall into one of three camps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;RAG&lt;/strong&gt; (Retrieval-Augmented Generation): Vector search over external notes or structured memories. It’s decent when you want to retrieve a few relevant examples for a specific fact or topic, but it’s not designed to surface every occurrence or reason about them in aggregate. RAG retrieves semantically similar, often out-of-context chunks, expecting the LLM to stitch them together, which can lead to incomplete or inaccurate results - sometimes even hallucinations when gaps are filled incorrectly. It also struggles with large datasets, where relevant information gets buried under noisy matches, and complex, multi-hop queries requiring reasoning (e.g., analyzing trends or causality). For example, if you ingest "Lord of the Rings" and ask for all disagreements between characters, RAG might surface vaguely related scenes rather than a comprehensive list. It’s also poor at associative tasks e.g., a user says “Today is my anniversary,” and RAG retrieves generic anniversary info instead of memories tied to the user’s relationship. This isn’t surprising given how it works - vectorizing the query and searching for the nearest text chunks in a flat list.&lt;/p&gt;

&lt;p&gt;The great post &lt;a href="https://www.letta.com/blog/rag-vs-agent-memory" rel="noopener noreferrer"&gt;here&lt;/a&gt; provides a solid breakdown of these shortcomings, though I don’t fully agree with their proposed fix (agentic RAG - see below).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most modern systems don’t rely on RAG alone - they pair it with structured metadata, keyword indices, or hybrid approaches (&lt;a href="https://weaviate.io/blog/hybrid-search-explained" rel="noopener noreferrer"&gt;see here&lt;/a&gt;) to try and make up for its limitations. The strengths of RAG are in its simplicity and unstructured nature (easy ingestion) but they are also its downfall. When it comes to implementing the kind of memory true persistent agents need - RAG won't do.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Agentic RAG&lt;/strong&gt;: The main idea behind agentic RAG is to take a standard RAG system and allow the LLM to query it multiple times in a loop - refining its queries and accumulating context until it has what it needs to answer. This enables more sophisticated reasoning and planning. Unfortunately, it inherits the same core limitations: the underlying retrieval is still vector-based RAG, so it suffers from context fragmentation, relevance drift, and shallow matches. The iterative nature also makes it computationally expensive, token-hungry, and often too slow for real-time use. It can get stuck in "loops of doom" or terminate prematurely without finding the necessary information. &lt;/p&gt;

&lt;p&gt;Although it improves upon RAG, Agentic RAG still falls short of what we intuitively think of as memory. That’s why I turned to something more structured...&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;To be clear&lt;/strong&gt;, not all use cases require real memory the way it's define in this post. If your goal is to retrieve a page from documentation or pull up a few helpful examples - RAG can be perfectly sufficient. Its simplicity makes it fast to implement and often “good enough” in practice. But once your system needs to reason over time, adapt to new experiences, or manage overlapping context - &lt;strong&gt;you’re outside RAG territory&lt;/strong&gt;. That’s where the memory gap becomes painfully obvious.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Graph RAG&lt;/strong&gt;: In a &lt;a href="https://dev.to/isaachagoel/read-this-before-building-ai-agents-lessons-from-the-trenches-333i"&gt;previous post&lt;/a&gt;, I described how I tried to compensate for RAG's limitations using agent-generated SQL queries over structured metadata. The core insight was simple: &lt;strong&gt;RAG lacks structure&lt;/strong&gt;. So what if we added it?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Graph-RAG&lt;/strong&gt; attempts exactly that. During ingestion, a large language model extracts entities and relationships from text and encodes them as nodes and edges in a graph. Later, retrieval happens by traversing that graph - e.g. walking outward from a node, filtering by relationship types, running path algorithms, and so on. Some frameworks even add a temporal dimension, which brings it a little closer to how we imagine human memory.&lt;/p&gt;

&lt;p&gt;It sounds promising on paper. After all, remembering is often associative - one idea leads to another. Graphs seem like a natural fit.&lt;/p&gt;

&lt;p&gt;However, this sophistication comes at a steep cost: the simplicity of traditional RAG is lost. Operations grow complex - entity resolution becomes a puzzle (e.g., does "the king," "king Arthur," or "He" refer to an existing "Arthur" node or a new entity?), and disambiguation is tricky (e.g., distinguishing between multiple Arthurs like the king, his father, or a peasant). Beyond that, challenges like conflict resolution, data invalidation (when new information arrives), and compaction arise.&lt;/p&gt;

&lt;p&gt;These are solvable, maybe... the &lt;strong&gt;real blocker&lt;/strong&gt; is &lt;strong&gt;schema design&lt;/strong&gt;:&lt;br&gt;&lt;br&gt;
How do you decide upfront what types of nodes and edges are relevant? In domains with rigid structure, like business workflows or e-commerce, you can get away with it, but for modeling generic memory? The kind of evolving, messy, contextual knowledge humans have? It falls apart.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Memory is not a tree of concepts. It's a living web of hypotheses, contradictions, associations, and revisions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;(PS: There are variants of Graph-RAG&lt;/em&gt; &lt;a href="https://arxiv.org/html/2404.16130v2" rel="noopener noreferrer"&gt;&lt;strong&gt;&lt;em&gt;like this one&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt; &lt;em&gt;that builds a tree structure, summarizing information as you move up. I didn’t explore it deeply since it felt even less suited for memory use cases.)&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Agentic Graph RAG&lt;/strong&gt;: Not sure it exists but no reason not to try :) &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As a software engineer, I was heavily attracted to Graph RAG and spent a long time playing with &lt;a href="https://github.com/getzep/graphiti/tree/main/graphiti_core" rel="noopener noreferrer"&gt;Graphiti&lt;/a&gt;. When I realised it was built with a chat between a single user and a single agent in mind, I even tried to implement my own customised version on top of Neo4j, tailored for my needs. But defining a good schema and ingesting long-form text into coherent, evolving memory graphs? That turned out to be &lt;strong&gt;really&lt;/strong&gt; hard. Humans build memory by revising beliefs, forming hypotheses, forgetting selectively and reading between the lines.&lt;/p&gt;

&lt;p&gt;Take something as ordinary as a chat log. Here's a real (simplified) example:&lt;/p&gt;

&lt;p&gt;Person A: "Oh, I'm gonna be so late..."&lt;br&gt;&lt;br&gt;
Person B: "What happened?"&lt;br&gt;&lt;br&gt;
Person A: "Ah, too embarrassed to ssy"&lt;br&gt;&lt;br&gt;
Person A: "*say"&lt;br&gt;&lt;br&gt;
Person B: "Lol, I bet you it's that roommate of yours again"&lt;br&gt;&lt;br&gt;
Person A: "That dude always forgets where he put our keys :("&lt;/p&gt;

&lt;p&gt;This tiny exchange contains a surprising amount of context and information: there's a roommate, the keys were lost, lateness resulted, and there's an ongoing joke or shared memory. Humans pick this up instinctively, but encoding it into a graph - resolving entities, inferring causality, surfacing associations is non-trivial. Where do you even start?&lt;/p&gt;

&lt;p&gt;Then, after grappling with it for a few days, it hit me: &lt;strong&gt;this was Symbolic AI all over again.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Like &lt;a href="https://en.wikipedia.org/wiki/Symbolic_artificial_intelligence" rel="noopener noreferrer"&gt;Symbolic AI&lt;/a&gt;, Graph RAG gives you the illusion of control: explicit structure, clean logic, tidy representations. But it breaks down the moment things get ambiguous, nuanced, or evolve over time. That neatness just doesn’t hold up in the real world. I pondered: what was the antidote to symbolic AI? deep learning and the transformers architecture...&lt;/p&gt;

&lt;p&gt;And then, something clicked.&lt;/p&gt;

&lt;p&gt;There already exists a system with exceptional recall - something that &lt;em&gt;can&lt;/em&gt; store associative, fuzzy, contextual information and resurface it later. &lt;strong&gt;LLMs, when it comes to their pre-trained data, already behave like they have memory.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;But the magic is in &lt;em&gt;how&lt;/em&gt; they remember. LLMs don’t store records in a database. They don't store records at all. The knowledge they absorb during training becomes embedded in their weights. And when you query them, the right patterns get activated.&lt;/p&gt;

&lt;p&gt;Try it: ask ChatGPT about something obscure, temporal, or requiring synthesis and instruct it to answer without using tools (no web search!). The results can be eerie. That thing remembers A LOT. It has zero trouble with timelines, contradictory information or anything else that traditional systems struggle with. &lt;a href="https://chatgpt.com/share/68875988-7c54-8008-94a4-c47b78a35650" rel="noopener noreferrer"&gt;Here’s an example.&lt;/a&gt; &lt;a href="[https://chatgpt.com/share/68875cd5-a3ac-8008-84ff-3a4e2ee197ef](https://chatgpt.com/share/68875cd5-a3ac-8008-84ff-3a4e2ee197ef)"&gt;Here is another one&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So what’s the problem? The weights are fixed, right? Once training ends, the model’s knowledge is frozen in time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Or is it?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I remembered that &lt;a href="[https://www.ibm.com/think/topics/fine-tuning](https://www.ibm.com/think/topics/fine-tuning)"&gt;fine-tuning&lt;/a&gt; updates model weights post training - usually to match a tone, format, or domain. It basically continues the training process after it was complete. So why not use it to &lt;em&gt;add&lt;/em&gt; memory? Why not encode new experiences or user knowledge directly into the weights?&lt;/p&gt;




&lt;h3&gt;
  
  
  Memory in the Weights? Maybe
&lt;/h3&gt;

&lt;p&gt;Unfortunately fine-tuning can't do it 😕. Turns out there's a well-known issue in continual learning called &lt;a href="https://arxiv.org/html/2308.08747v5" rel="noopener noreferrer"&gt;&lt;strong&gt;catastrophic forgetting&lt;/strong&gt;&lt;/a&gt;: when you fine-tune a model on new knowledge, it inevitably overwrites older capabilities - the more you fine-tune the more of the original knowledge you lose. Not ideal if you're trying to simulate a persistent, growing memory.&lt;/p&gt;

&lt;p&gt;That realization sent me down a rabbit hole of academic papers. Unsurprisingly, I wasn’t the first person to chase this idea and I quickly found some genuinely exciting research that tries to do what fine-tuning can’t. Two papers stood out: &lt;a href="https://arxiv.org/html/2402.04624v2" rel="noopener noreferrer"&gt;&lt;strong&gt;MemoryLLM&lt;/strong&gt;&lt;/a&gt; and &lt;a href="https://arxiv.org/html/2504.21239v1" rel="noopener noreferrer"&gt;&lt;strong&gt;MEGa&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The MEGa paper was fascinating and advanced but didn’t release any code. MemoryLLM, on the other hand, did and their approach was clever: rather than modifying the entire model, they introduced a &lt;strong&gt;dedicated memory region&lt;/strong&gt; within the weights. The base model stays untouched, while memory is isolated, updated, and read from dynamically at inference. They even accounted for "forgetting" older, less frequently used information. &lt;/p&gt;

&lt;p&gt;And the most beautiful thing: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Since the memories were encoded in the weights - none of the context window limitations apply.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I instantly knew had to try it first hand.&lt;/p&gt;




&lt;h3&gt;
  
  
  Getting Hands-On: Memory LLM
&lt;/h3&gt;

&lt;p&gt;I cloned &lt;a href="[https://github.com/wangyu-ustc/MemoryLLM](https://github.com/wangyu-ustc/MemoryLLM)"&gt;the repo&lt;/a&gt; and got it running on &lt;a href="[https://console.runpod.io/](https://console.runpod.io/)"&gt;a remote machine&lt;/a&gt; with a beefy GPU (after burning a few hours on trial an error and fighting with Python dependencies etc). &lt;/p&gt;

&lt;p&gt;When I went over the codebase, one thing immediately stood out: the researchers had actually &lt;strong&gt;modified the inference logic&lt;/strong&gt; of the model to support reading from and writing to memory. Not the training pipeline, &lt;strong&gt;the live inference code&lt;/strong&gt;. That’s a part of the stack us developers never go near. But seeing it altered made something click for me:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We’re used to thinking of models as black boxes that you train or fine-tune. But you can also &lt;strong&gt;intervene in how they run&lt;/strong&gt;, almost like patching application code. That’s powerful and honestly under-explored by engineers.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then I saw they were only supporting Llama 3 - a relatively old, weak model and that highlighted something else:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The research mindset is very different from the engineering mindset.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Researchers prototype with the goal of publishing a paper. That means small models, clean baselines, simple benchmarks, limited scope. Engineers, on the other hand build with the goal of reaching a working, usable POC that can be used in the real world - not in a lab. They reach for the most powerful tools they can find (for open models that would be LLaMA 4, DeepSeek R1 or Kimi at the time of writing this). The last thing we engineers want is to be bottlenecked by a weak model. We instinctively ask: "will this scale?"&lt;/p&gt;

&lt;p&gt;But here’s the tradeoff: those stronger models often come with much more complex internals: Mixture of Experts, longer pipelines, finicky tokenizers, harder fine-tuning. They’re not well documented and not easy to poke at unless you have serious time, compute, and domain knowledge. The researcher is making a pragmatic choice and one that makes sense for them, but leaves us wanting.&lt;/p&gt;

&lt;p&gt;Still, I had a plan: if MemoryLLM could reliably store and retrieve memory (even in small scales), I could wrap it with a smarter agent that decides what’s worth remembering, when to save it, and how to use it in the future. I could scale horizontally with multiple instances. I didn’t need perfection, just a signal that the core mechanism worked.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;And if it &lt;em&gt;did&lt;/em&gt; work, it would open up a whole new avenue: not just tweaking prompts or retraining models, but actually &lt;strong&gt;engineering&lt;/strong&gt; memory systems by intervening in the model’s runtime itself.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Reality Check: Does MemoryLLM Work?
&lt;/h3&gt;

&lt;p&gt;I had high hopes. The benchmarks on the paper looked great but I was about to swallow some bitter medicine. &lt;/p&gt;

&lt;p&gt;MemoryLLM offers two modes: &lt;code&gt;chat&lt;/code&gt; and &lt;code&gt;mplus&lt;/code&gt;. The latter increases memory storage and improves retrieval, but it isn’t optimized for conversational flows - it tends to keep generating past the user’s question as if continuing the chat history. I tested both.&lt;/p&gt;

&lt;p&gt;I knew that in the paper they ingested short snippets but for my use case that wasn't practical. Consider the same example we looked at before:&lt;/p&gt;

&lt;p&gt;Person A: "Oh, I'm gonna be so late..."&lt;/p&gt;

&lt;p&gt;Person B: "What happened?"&lt;/p&gt;

&lt;p&gt;Person A: "Ah, too embarrassed to ssy"&lt;/p&gt;

&lt;p&gt;Person A: "*say"&lt;/p&gt;

&lt;p&gt;Person B: "Lol, I bet you it's that roommate of yours again"&lt;/p&gt;

&lt;p&gt;Person A: "That dude always forgets where he put our keys :("&lt;/p&gt;

&lt;p&gt;LLMs have no problem understanding this like a human would but only if it is ingested as a whole. If we ingest it line by line it loses all meaning.&lt;br&gt;
Ingesting full "episodes" was possible in my previous experiments, when I was playing with Graphiti, but not quite so with MemoryLLM. &lt;/p&gt;

&lt;p&gt;When I tried to ingest examples like the conversation above, the results were disappointing. Sometimes the input was ignored entirely. Other times, the responses were hallucinated or incoherent. When I carefully mimicked the benchmark setup, including their custom attention masks, and drastically shortened the inputs - I could get semi-coherent replies, but still nothing close to usable in an actual system.&lt;/p&gt;

&lt;p&gt;Digging into the benchmark code revealed why: the test setup was &lt;strong&gt;highly unrealistic&lt;/strong&gt;. Simple, isolated prompt-response pairs. No real dialogue. No sustained context. In short, nothing that resembled real-world use.&lt;/p&gt;

&lt;p&gt;This isn’t a knock on the researchers - they weren’t trying to build a production agent and I do think their ideas are remarkable. But it served as a bitter reminder: synthetic benchmarks can look impressive while masking critical limitations.&lt;/p&gt;

&lt;p&gt;To be fair, I’m not an AI researcher. Maybe I missed something. But after days of config tweaks, prompt engineering, and test cases, I’m pretty confident: this is an exciting idea but it’s still in its infancy. Not ready for real-world agents.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Contrast
&lt;/h3&gt;

&lt;p&gt;Here’s the kicker: I was using Claude 4 and o3 &lt;strong&gt;inside my IDE&lt;/strong&gt; to help write, test, and troubleshoot all of this. The difference was staggering. These models were grounded and nuanced. I’d paste in the same kinds of messy conversations I tested MemoryLLM on, and they’d instantly parse the implied context, draw the right conclusions, and respond meaningfully.&lt;/p&gt;

&lt;p&gt;It drove the following point home:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We’re sitting on a goldmine that’s frozen in time. &lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  So What Now?
&lt;/h3&gt;

&lt;p&gt;I still believe that &lt;strong&gt;embedding memory in the model itself&lt;/strong&gt; is the long-term path. It’s the only direction that could eventually support agents that learn, grow, and evolve the way humans do. But I now better understand why real-world teams still rely on RAG, vector DBs, and graph overlays: they’re accessible, composable, debug-able and well understood. You can build something useful without re-architecting a transformer.&lt;/p&gt;

&lt;p&gt;Still, I wonder if we, as engineers, should begin crossing that boundary - learning to intervene not just at the API layer, but in the internals of open-weight models - bringing our engineering mindset to the table. I mean, how difficult can it be? 😉&lt;/p&gt;

&lt;p&gt;For now, I’m keeping one foot in each world: shipping practical tools with what’s available, and probing the frontier to see what might be possible.&lt;/p&gt;

&lt;p&gt;If I find anything that shifts the landscape, I’ll write a follow-up.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;PS&lt;/strong&gt;: If you're working on any similar ideas, I’d love to hear from you. Let's compare scars.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Why "10x Employees" Don’t Actually Do Their Jobs (And Why That’s a Good Thing)</title>
      <dc:creator>Isaac Hagoel</dc:creator>
      <pubDate>Fri, 04 Apr 2025 06:26:37 +0000</pubDate>
      <link>https://dev.to/isaachagoel/why-10x-employees-dont-actually-do-their-jobs-and-why-thats-a-good-thing-33op</link>
      <guid>https://dev.to/isaachagoel/why-10x-employees-dont-actually-do-their-jobs-and-why-thats-a-good-thing-33op</guid>
      <description>&lt;p&gt;&lt;strong&gt;The secret no one tells you:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;em&gt;10x employees aren’t employees at all. They’re undercover agents.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In 20 years working with (and being) these outliers, I’ve learned one truth: &lt;strong&gt;10xers don’t do their job, they hijack it.&lt;/strong&gt; They infiltrate organizations through roles, play the part just enough to avoid HR alarms, then execute a silent coup against mediocrity. Let me explain.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Great Lie of Job Descriptions
&lt;/h3&gt;

&lt;p&gt;Your job description is a trap.&lt;/p&gt;

&lt;p&gt;Engineer? Your “success” means completing tickets, not solving problems.&lt;br&gt;&lt;br&gt;
Manager? Your worth is tied to hitting stale KPIs, not leading revolutions.&lt;br&gt;&lt;br&gt;
Play by these rules, and you’ll peak at “1.5x employee”, the slightly faster cog in a broken machine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;10xers reject this.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
They treat their job description like a burner phone: use it to gain access, then toss it when it’s time to act.&lt;/p&gt;




&lt;h3&gt;
  
  
  Anatomy of a Corporate Rebel
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;A 10x employee:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Delivers &lt;strong&gt;10x the impact&lt;/strong&gt; (not output) of peers&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Operates like a founder, not a follower&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Rewrites rules instead of repeating them&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Case Study: The Rogue Cloud Manager&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
My first boss at Intel was given a playbook:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;“Manage this tiny internal cloud team”&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;“Don’t rock the boat”&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;He burned it.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Built a cross-region compute sharing system &lt;strong&gt;without approval&lt;/strong&gt; (initially)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Saved Intel &lt;strong&gt;$40M+&lt;/strong&gt; in wasted resources&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Turned our scrappy team into the &lt;strong&gt;global cloud authority&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;His secret? &lt;strong&gt;He ignored his “manager” title and acted like the CEO of Intel’s future.&lt;/strong&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Why Systems Hate 10xers
&lt;/h3&gt;

&lt;p&gt;Organizations are designed to &lt;strong&gt;neutralize threats&lt;/strong&gt;, even good ones.&lt;/p&gt;

&lt;p&gt;When you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;🚨 Fix problems you’re “not responsible for”&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🚨 Ship POCs instead of begging for permission&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🚨 Ask “Why?” three times in exec meetings&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;…you trigger defenses. Bureaucracy flares up. Status quo defenders mobilize.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This isn’t a bug - it’s the system working as intended.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Most 10xers get contained or expelled. The survivors? They become &lt;strong&gt;agents of momentum&lt;/strong&gt;, cutting through inertia with energy, candor, and an allergy to complacency.&lt;/em&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  The 10x Playbook (Steal This)
&lt;/h3&gt;

&lt;p&gt;Want to matter more than your role allows?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Find the delta&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;em&gt;What’s the company’s stated mission vs. its actual daily work?&lt;/em&gt;&lt;br&gt;&lt;br&gt;
Example: If your bank claims “financial empowerment” but up-sells debt, fix THAT.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Break the Unwritten Rules&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;em&gt;Spot the silent killers: processes everyone hates, limits nobody questions, work that exists to feed other work.&lt;/em&gt;&lt;br&gt;&lt;br&gt;
Example: That “mandatory” weekly report nobody reads? The 8-layer approval chain for minor changes? Burn. Them. Down. (Careful playing with fire, though.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Operational Humility&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Ask like a novice, act like a surgeon&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Listen to frontline complaints (they’re goldmines for systemic flaws)&lt;/li&gt;
&lt;li&gt;Say “I don’t get it. Can you walk me through why this works?” to expose shaky logic&lt;/li&gt;
&lt;li&gt;Treat every critique as data, not dissent&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Key&lt;/strong&gt;: Your goal isn’t to be right. It’s to uncover what’s right.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Build first, apologize never&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
10xers know it’s easier to ship a working prototype than get a “maybe” in a roadmap meeting.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Weaponize First Principles&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Next time someone says “We do it this way because…”, respond: &lt;em&gt;“Show me the math.”&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Recruit Allies, Not Followers&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Find other closet 10xers. Trade favours. Build parallel power structures.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;em&gt;The 10xer’s Edge: Confidence Without Condescension&lt;/em&gt;&lt;br&gt;&lt;br&gt;
True 10xers are clarity ninjas, not know-it-alls. They ask “What if we…” instead of “You should…” because revolutions happen &lt;em&gt;with&lt;/em&gt; people, not &lt;em&gt;to&lt;/em&gt; them&lt;/p&gt;




&lt;h3&gt;
  
  
  How to 10x Without Getting Fired (Mostly)
&lt;/h3&gt;

&lt;p&gt;Being a 10xer isn’t about martyrdom. Here’s how to bend the rules without snapping your career:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Pick Your Battles Like a Strategist&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Burn down &lt;em&gt;one&lt;/em&gt; pointless process at a time. Organizations tolerate a controlled burn; they extinguish wildfires.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Bank Trust First&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Deliver a few wins &lt;em&gt;within&lt;/em&gt; the system early on. Once you’re the “person who fixed X,” you get leeway to break Y.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Frame Your Rebellion as an Experiment&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
“I’m testing a hypothesis to save us 20% latency. Can I prototype this quietly?” sounds better than “Your API design is trash.”&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Document Everything (CYA 101)&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
When you bypass a rule, leave a breadcrumb trail:&lt;br&gt;&lt;br&gt;
&lt;em&gt;“Discussed with team on 4/2 – consensus to prioritize customer impact over process.”&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Know When to Fold&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
If the system rejects your fix, document the dead end, then walk away. Save your energy for winnable wars.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Become a servant leader&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Your rebellion is a service, not an ego trip. The goal isn’t to be the hero - it’s to make &lt;em&gt;everyone&lt;/em&gt; better by aligning the system with what’s right.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Pro tip:&lt;/strong&gt; Let others claim your ideas as theirs. Silent 10xers care more about impact than attribution.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Price of 10x
&lt;/h3&gt;

&lt;p&gt;Choose your sacrifice:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option A:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;✅ Predictable promotions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ Calm Fridays&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ A LinkedIn bio that matches your obituary&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Option B:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;🔥 Late-night “aha!” moments&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🔥 Political grenades thrown your way&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🔥 A legacy that outlives your tenure&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There’s no middle ground.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Final Test
&lt;/h3&gt;

&lt;p&gt;When you’re 65, rocking on some porch, which story do you want to tell?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;“I color-coded Jira tickets like a boss!”&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;“I rewrote the rules and changed how we _&lt;/em&gt;_____&lt;em&gt;.”&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;10xers pick the second script, even if they get edited out of the corporate memoir.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So... do you have what it takes?&lt;/strong&gt;  &lt;/p&gt;

</description>
      <category>career</category>
      <category>watercooler</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>I’m a 20-Year Engineer – AI Coding Tools Are My New Oxygen (But They’re Toxic If You Breathe Too Deep)</title>
      <dc:creator>Isaac Hagoel</dc:creator>
      <pubDate>Sat, 29 Mar 2025 23:56:37 +0000</pubDate>
      <link>https://dev.to/isaachagoel/im-a-20-year-engineer-ai-coding-tools-are-my-new-oxygen-but-theyre-toxic-if-you-breathe-too-3h5n</link>
      <guid>https://dev.to/isaachagoel/im-a-20-year-engineer-ai-coding-tools-are-my-new-oxygen-but-theyre-toxic-if-you-breathe-too-3h5n</guid>
      <description>&lt;p&gt;&lt;em&gt;Let’s be clear: I'm totally hooked on coding with AI and not looking back. Here’s how to use it like a stimulant, not a crutch&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;AI coding assistants aren’t just helpful – &lt;strong&gt;they’re a cognitive exoskeleton&lt;/strong&gt;. I code 3x faster with Cursor, ship prototypes in hours instead of days, and offload the work that makes my brain feel like overcooked ramen. But here’s the catch: &lt;em&gt;every line of AI code is a Faustian bargain&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why I’m Addicted (And You Should Be Too)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;AI isn’t “helpful” – it’s a force multiplier for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Killing yak shaves&lt;/strong&gt; (Bash scripts, config hell, regex, TypeScript puzzlers, SQL gymnastics)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Bypassing syntax paralysis&lt;/strong&gt; (How &lt;em&gt;do&lt;/em&gt; you format dates in Swift again? Why &lt;em&gt;wouldn't&lt;/em&gt; this API cooperate?)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Prototyping at methamphetamine speed&lt;/strong&gt; (Need a React table with client-side sorting? Generated in 8 seconds)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Last month, I prototyped a RAG tool for my AI agent with 87% AI-generated code (local vector DB, embeddings, ranking – the whole circus). &lt;strong&gt;2 hours&lt;/strong&gt; instead of 2 days. &lt;em&gt;Was it perfect?&lt;/em&gt; Hell no. But it let me start iterating &lt;strong&gt;immediately&lt;/strong&gt;. Now that we're shipping? I’m rewriting 87% manually – with AI as my WD-40 for stubborn code bolts.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Tech Debt Paradox
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;AI-generated code isn’t “bad” – it’s &lt;em&gt;strategically irresponsible&lt;/em&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I treat it like a high-interest credit card:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;SWIPE FREELY&lt;/strong&gt; for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Throwaway code (one-off scripts, spike solutions)&lt;/li&gt;
&lt;li&gt;Boilerplate (mocks, DTOs, CRUD skeletons)&lt;/li&gt;
&lt;li&gt;“I just need this to work once” moments (local DB migrations)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;PAY DOWN IMMEDIATELY&lt;/strong&gt; when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It touches business logic&lt;/li&gt;
&lt;li&gt;Performance/security/privacy matter (read: production)&lt;/li&gt;
&lt;li&gt;You’re past exploration and need precision&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Example:&lt;/em&gt; Last month Claude wrote 5 Jest mocks + 10 unit tests for an unfamiliar codebase. &lt;strong&gt;45 minutes saved. 2 hours of existential crisis avoided.&lt;/strong&gt; Tech debt? 15 minutes fixing incoherent tests. &lt;strong&gt;Net win!&lt;/strong&gt; 🎉&lt;/p&gt;




&lt;h3&gt;
  
  
  How to Inhale Without Choking
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;My rules for AI-as-breathing-apparatus:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AI First, Human Always&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Generate → Review → &lt;em&gt;Rewrite/Question&lt;/em&gt;. I refactor AI code like it owes me money. Sometimes I reject 100% of its suggestions – but even its wrong answers spark better solutions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Complexity Alarm&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
AI loves Rube Goldberg solutions. Ask: &lt;em&gt;“Could a junior understand this sober at 3AM?”&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Architecture is Human-Only&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
AI sees files, not systems. Never let it decide service boundaries – it’s like letting a golden retriever plan city infrastructure.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Rubber Duck 2.0&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
“Explain tradeoffs between JWT session strategies” → &lt;em&gt;Then decide&lt;/em&gt;. Treat it like a Wikipedia article – useful overview, questionable details.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Soul-as-a-Service&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Let AI borrow your coding soul like a library book – but always check it back in. Late fees hurt.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  For Juniors: How to Level Up Without Getting Played
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;AI won’t make you a better coder – &lt;em&gt;you&lt;/em&gt; make you a better coder.&lt;/strong&gt; Here’s how to avoid becoming an AI puppet:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Practice Raw Coding Like It’s the Gym&lt;/strong&gt;
AI is your protein shake, not your workout. If 90% of your code is AI-generated, you’re building prompt engineering muscles (useful!) but &lt;strong&gt;atrophying actual coding skills&lt;/strong&gt; (disastrous).

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mandatory manual reps:&lt;/strong&gt; Code one full feature/week from scratch. No autocomplete, no Copilot.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read AI’s output line-by-line&lt;/strong&gt; like it’s your ex’s text messages – with skeptical intensity.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You’re the Architect, AI’s the Hammer&lt;/strong&gt;
Treat AI like a power tool:

&lt;ul&gt;
&lt;li&gt;“Why did you use a HashMap here?” → Make it justify every choice&lt;/li&gt;
&lt;li&gt;“Show me 3 alternative approaches” → Then &lt;strong&gt;pick none of them&lt;/strong&gt; and write your own&lt;/li&gt;
&lt;li&gt;If you couldn’t explain the code to a intern, &lt;strong&gt;you don’t understand it&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build Your BS Detector&lt;/strong&gt;
AI’s wrong answers are golden learning opportunities: 

&lt;ul&gt;
&lt;li&gt;When it suggests an overcomplicated design pattern, ask: &lt;em&gt;“What problem is this solving?”&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;When it makes a subtle mistake (timezone handling, API rate limits), &lt;strong&gt;dig into why&lt;/strong&gt; it failed&lt;/li&gt;
&lt;li&gt;Every AI error is a free lesson in critical thinking&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The “Could I Build This Blindfolded?” Rule&lt;/strong&gt;
Only use AI for:

&lt;ul&gt;
&lt;li&gt;Tasks you &lt;strong&gt;already understand&lt;/strong&gt; (boilerplate, mocks)&lt;/li&gt;
&lt;li&gt;Explorations you’ll &lt;strong&gt;immediately validate&lt;/strong&gt; (spike solutions)
If you’re using AI to hide from learning fundamentals, you’re just stacking debt.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  Why Seniors Survive the AI Apocalypse
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Experience lets me:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Spot “confidently wrong” code&lt;/strong&gt; like a bloodhound (missed timezones, security holes, 🤮 code duplication)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Surgically extract value&lt;/strong&gt; (Keep AI’s 80% SQL JOIN, fix its 20% N+1 query disaster)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Embrace strategic debt&lt;/strong&gt; (This script dies Friday anyway – let AI write its epitaph)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Final Take:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
AI is coding’s cheat code – but only if you’ve already beaten the game. Use it to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Supercharge trivial work&lt;/strong&gt; (Your brain’s for hard problems)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Buy speed with intentional debt&lt;/strong&gt; (Like taking a loan to close a deal)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Avoid context-switching&lt;/strong&gt; (No more 15-tab Google spirals)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But &lt;strong&gt;never forget:&lt;/strong&gt; AI code is radioactive. Handle it with lead gloves.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I’m now 73% cyborg. Juniors – hit me with your AI horror stories. Seniors – share your power moves. Let’s code.&lt;/em&gt; 👇&lt;/p&gt;

</description>
      <category>programming</category>
      <category>career</category>
      <category>webdev</category>
      <category>ai</category>
    </item>
    <item>
      <title>Read This Before Building AI Agents: Lessons From The Trenches</title>
      <dc:creator>Isaac Hagoel</dc:creator>
      <pubDate>Sun, 23 Mar 2025 10:29:40 +0000</pubDate>
      <link>https://dev.to/isaachagoel/read-this-before-building-ai-agents-lessons-from-the-trenches-333i</link>
      <guid>https://dev.to/isaachagoel/read-this-before-building-ai-agents-lessons-from-the-trenches-333i</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key Takeaways&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🛠️ &lt;strong&gt;Hybridize&lt;/strong&gt;: Combine LLMs with traditional code for reliability and creativity.
&lt;/li&gt;
&lt;li&gt;🧩 &lt;strong&gt;Specialize&lt;/strong&gt;: Use multiple agents to avoid complexity thresholds.
&lt;/li&gt;
&lt;li&gt;📐 &lt;strong&gt;Structure&lt;/strong&gt;: Enforce outputs with Zod/Pydantic schemas to reduce hallucinations.
&lt;/li&gt;
&lt;li&gt;🔍 &lt;strong&gt;Agentic RAG&lt;/strong&gt;: Let agents control retrieval for dynamic workflows.
&lt;/li&gt;
&lt;li&gt;⚡ &lt;strong&gt;Optimize&lt;/strong&gt;: Balance token usage, speed, and quality with parallel tool calls.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;




&lt;p&gt;Over the last few months, I've been diving deep into the world of AI agents. What started as side projects and general curiosity has evolved into actual work projects. This means I'm in the process of crossing over from hobbyist to pro (by definition, you're not a pro until you get paid to do whatever it is you're doing!) and from toy apps to ones with real users.&lt;/p&gt;

&lt;p&gt;I'm quite early in my journey and still have so much to learn, yet I'm surprised by how many challenges I've encountered despite reading blogs, watching videos, etc. There are insights that aren't widely shared yet, and this post aims to fill that gap.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Agent Building Journey &lt;em&gt;(A Brief Overview)&lt;/em&gt;
&lt;/h2&gt;

&lt;p&gt;For context, here's what I've worked on so far:&lt;/p&gt;

&lt;h3&gt;
  
  
  Toy Projects
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;LinkedIn Job Finder&lt;/strong&gt;: Split tasks between Playwright scripts (link scraping) and agents (job rating). Initially tried one agent for everything, then realized specialized agents worked better.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;QA Automation&lt;/strong&gt;: Separated test planning (agent) from execution (code). One agent creates test plans by analyzing web pages; code then spawns a second agent to execute tests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom Framework&lt;/strong&gt;: Built after exploring existing frameworks like CrewAI and finding their abstractions didn't match my needs. This exploration helped me discover which abstractions actually make sense for my use cases.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Work Projects &lt;em&gt;(Limited Details for Confidentiality)&lt;/em&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Integration Into Pre-existing Codebases&lt;/strong&gt;: Several POCs integrating LLMs into existing apps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Product Analytics Assistant&lt;/strong&gt;: An internal tool leveraging agentic RAG and other tools, implemented from scratch. Now launching as a closed beta.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What is an AI Agent?
&lt;/h2&gt;

&lt;p&gt;An AI agent orchestrates multiple LLM calls to &lt;strong&gt;make decisions&lt;/strong&gt;, &lt;strong&gt;use tools&lt;/strong&gt;, and &lt;strong&gt;achieve goals&lt;/strong&gt;—beyond single prompts. It's code that wraps around LLM calls, allowing the AI to determine its own path toward a goal rather than just generating a response to a single prompt.&lt;/p&gt;

&lt;p&gt;With that said, in real-world applications, I've found that making single LLM calls for specific tasks is often quite practical. I prefer to think of these as 'single-step, tool-less agents' since this mental model is more useful than drawing an artificial distinction between agents and LLM calls. Most apps need a mix of both approaches.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why and When Agents?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  When Agents Excel
&lt;/h3&gt;

&lt;p&gt;LLMs can do things that normal code simply can't - tasks for which there is no conventional algorithm:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generating creative content ("make this text children friendly")&lt;/li&gt;
&lt;li&gt;Making subjective judgments with nuance (e.g., grading job postings based on fuzzy preferences)&lt;/li&gt;
&lt;li&gt;Extracting meaning from unstructured data (e.g., key takeaways from documents)&lt;/li&gt;
&lt;li&gt;Adaptive control flow - Instead of coding rigid "if" conditions for every situation, you provide guidelines and the LLM adapts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They also bring unique benefits when used to mimic traditional ML systems, like recommenders, because they can handle any text or images without predefined patterns and without needing to train on your data.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Golden Rule: Code When Possible
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Always ask&lt;/strong&gt;: &lt;em&gt;"Can I code this without losing functionality?"&lt;/em&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Traditional code for mechanical tasks (scraping, loops).
&lt;/li&gt;
&lt;li&gt;🤖 Agents for reasoning/adaptation.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;Whenever you consider giving a task to an agent, first ask yourself: "Can I code this whole thing or some part of it without losing functionality?"&lt;/p&gt;

&lt;p&gt;If the answer is yes, code it and leave to the agent only what normal code cannot do. Traditional code is orders of magnitude more:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Performant&lt;/li&gt;
&lt;li&gt;Accurate&lt;/li&gt;
&lt;li&gt;Predictable &lt;/li&gt;
&lt;li&gt;Testable&lt;/li&gt;
&lt;li&gt;Cost-effective (no token charges)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I learned this lesson the hard way with my LinkedIn job finder. Initially, I asked an agent with browser capability to visit LinkedIn and collect job links. The performance was poor and the agent got confused by the virtual list within a scrolling container. Eventually, I replaced this with a simple Playwright script for link collection, making the system much faster, more accurate, and cheaper.&lt;/p&gt;

&lt;p&gt;The tradeoff? The Playwright approach might break if the page markup changes. But for mechanical tasks like data collection, web scraping, or file operations, traditional code is almost always superior.&lt;/p&gt;

&lt;p&gt;Similarly, for control flow, if you need to loop through documents, don't tell an agent to "for each document in this list do X." Instead, use a normal loop and spawn an agent for each document (potentially in parallel for efficiency).&lt;/p&gt;

&lt;h3&gt;
  
  
  Hybrid Is Best
&lt;/h3&gt;

&lt;p&gt;The most powerful agentic applications combine LLMs with conventional code in a synergistic way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Traditional code&lt;/strong&gt; handles deterministic tasks, data processing, and integration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LLM agents&lt;/strong&gt; handle understanding, reasoning, creativity, and adaptation&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Our Hypothetical Example: A Marketing Email System
&lt;/h2&gt;

&lt;p&gt;Throughout this post, I'll use a hypothetical marketing email platform as an example. This system creates personalized product recommendations and illustrates many key patterns.&lt;/p&gt;

&lt;p&gt;The architecture consists of four specialized agents:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Data Collector Agent&lt;/strong&gt; - Gathers customer information from databases and public sources&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Product Selector Agent&lt;/strong&gt; - Analyzes customer data to recommend relevant products&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Writer Agent&lt;/strong&gt; - Creates personalized email content using brand templates&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reviewer/Editor Agent&lt;/strong&gt; - Ensures quality control and requests revisions&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This structure demonstrates how agents can collaborate while maintaining focused responsibilities, which brings us to our first critical insight.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note on Code Examples&lt;/strong&gt;: All code examples in this post are highly simplified for clarity and illustrative purposes. They're meant to convey concepts rather than provide production-ready implementations.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Critical Insights for Building Effective Agents
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Respect Complexity Thresholds
&lt;/h3&gt;

&lt;p&gt;Every model fails past a certain complexity threshold. When you cross this threshold, the model struggles to follow instructions and hallucinations increase exponentially.&lt;/p&gt;

&lt;p&gt;When developers ask "Why do I need multiple agents?" I know they haven't built real agent systems yet. Once you hit a complexity threshold, you have three options:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Reduce requirements (simplify the task)&lt;/li&gt;
&lt;li&gt;Upgrade to a better model (usually more expensive plus the ceiling is the best model available)&lt;/li&gt;
&lt;li&gt;Split the task across specialized agents&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It's not unlike people - there's a limit to how many instructions a person can follow effectively and how many tools they can wield.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example from our marketing system:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In our marketing email system, a single agent trying to handle data collection, product selection, writing, and reviewing would struggle. Breaking this into specialized agents creates much more reliable results.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before&lt;/strong&gt; (Single Agent):&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;// Prone to inconsistency and hallucinations&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;marketingAgent&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;Agent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Marketing Email Agent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Handle collecting customer data, selecting products, writing emails, 
                and reviewing content quality, maintaining brand voice...`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;customerDataTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;linkedinTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;interactionHistoryTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
          &lt;span class="nx"&gt;productCatalogTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;templateTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;emailSender&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;After&lt;/strong&gt; (Specialized Agents):&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;// Data collection is now focused and structured&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dataCollectorAgent&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;Agent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Data Collector Agent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Gather relevant customer data from internal systems and public sources.
                Focus on professional background, interests, and past interactions...`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;customerDbTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;linkedinTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;interactionHistoryTool&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Additional specialized agents would follow a similar pattern&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach succeeds because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each agent excels at a narrower, well-defined task&lt;/li&gt;
&lt;li&gt;Prompts can be shorter and more focused&lt;/li&gt;
&lt;li&gt;Error recovery is simpler (one malfunctioning agent doesn't derail everything)&lt;/li&gt;
&lt;li&gt;Each step can be optimized, tested, and reused independently&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Consider using multiple specialized agents when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The task can be naturally broken into subtasks&lt;/li&gt;
&lt;li&gt;Different tasks require different types of reasoning&lt;/li&gt;
&lt;li&gt;The prompt would otherwise become unwieldy&lt;/li&gt;
&lt;li&gt;You need to maintain different states for different parts of the process&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Structured Outputs Are Non-Negotiable
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://platform.openai.com/docs/guides/structured-outputs" rel="noopener noreferrer"&gt;Structured outputs&lt;/a&gt; (JSON schemas) completely transform agent development. This feature allows you to specify an exact output format and guarantees the model returns data as specified.&lt;/p&gt;

&lt;p&gt;The benefits are both obvious and subtle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Expected benefit&lt;/strong&gt;: No more begging the model for correct formatting or retrying on malformed outputs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unexpected benefit&lt;/strong&gt;: Schemas can force the model to follow specific reasoning patterns and make better decisions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example: Product Selector Agent Schema&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;productRecommendationSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;customerSummary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Brief summary of customer needs based on data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;recommendedProducts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;productName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;relevanceScore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;How relevant for this customer (1-10)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;justification&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Detailed reasoning for why this product matches customer needs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;sellingPoints&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Key points to emphasize in marketing&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;fallbackRecommendations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;productName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;reasonForInclusion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Why this is included as a fallback option&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="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Secondary recommendations if primary ones don't resonate&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;This schema does more than format data—it forces the agent to think deeply about product selection:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The &lt;code&gt;justification&lt;/code&gt; field requires detailed reasoning for each recommendation&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;relevanceScore&lt;/code&gt; field forces ranking and prioritization&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;sellingPoints&lt;/code&gt; array ensures usable content for the Writer Agent&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By requiring this structured approach, we get more thoughtful, consistent recommendations rather than superficial matches. The model must actually think through its choices &lt;em&gt;and&lt;/em&gt; we make it easy to &lt;code&gt;JSON.parse&lt;/code&gt; and continue processing it using code (or other agents). &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key takeaway&lt;/strong&gt;: 📐&lt;em&gt;Structured outputs make calling a LLM / Agent akin to calling any other remote API&lt;/em&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  3. Language Choices: TypeScript vs. Python
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;TypeScript&lt;/th&gt;
&lt;th&gt;Python&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Static Type System&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ First-class, enforced at compile time&lt;/td&gt;
&lt;td&gt;🟡 Optional type hints, not enforced&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Runtime Validation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Zod&lt;/td&gt;
&lt;td&gt;✅ Pydantic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Async Support&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Native&lt;/td&gt;
&lt;td&gt;🟡 Requires asyncio&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ML Libraries&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;🟡 Growing&lt;/td&gt;
&lt;td&gt;✅ Dominant&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;JSON Handling&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Native&lt;/td&gt;
&lt;td&gt;🟡 Import json&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Developer Tools&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Excellent&lt;/td&gt;
&lt;td&gt;✅ Good&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Package Management&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ npm/yarn with package.json and lock files&lt;/td&gt;
&lt;td&gt;🟡 pip with requirements.txt (no lock by default) or Poetry/Pipenv (with locks)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Both languages are excellent choices for agent development, with different strengths:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Python&lt;/strong&gt; has established itself as the primary language in the AI/ML ecosystem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rich ecosystem of ML and AI libraries with first-class support&lt;/li&gt;
&lt;li&gt;Most agent frameworks and tutorials are Python-first&lt;/li&gt;
&lt;li&gt;Excellent data processing capabilities&lt;/li&gt;
&lt;li&gt;Familiar to data scientists and ML practitioners&lt;/li&gt;
&lt;li&gt;Strong support through libraries like Pydantic for structured outputs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;TypeScript&lt;/strong&gt; offers compelling advantages for software engineers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Static typing helps prevent runtime errors and enables better tooling&lt;/li&gt;
&lt;li&gt;First-class support for structured outputs via Zod integration&lt;/li&gt;
&lt;li&gt;Native JSON handling simplifies working with API responses&lt;/li&gt;
&lt;li&gt;Robust async/await pattern for managing concurrent operations&lt;/li&gt;
&lt;li&gt;Unified language for both frontend and backend development&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My personal preference leans toward TypeScript because:&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;// Example: Type safety with runtime validation using Zod&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;productRecommendationSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;customerSummary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;recommendedProducts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;relevanceScore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a number between one 1 and 10),
    justification: z.string()
  }))
});

// TypeScript automatically infers the correct type
type ProductRecommendation = z.infer&amp;lt;typeof productRecommendationSchema&amp;gt;;

// You get autocompletion and type checking when working with validated data
function processRecommendation(rec: ProductRecommendation) {
  // Access properties with confidence - TypeScript knows the structure
  const topProduct = rec.recommendedProducts.sort((a, b) =&amp;gt; 
    b.relevanceScore - a.relevanceScore
  )[0];

  return `Top recommendation: ${topProduct.justification}`;
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This combination of static typing, runtime validation, and excellent tooling significantly improves developer confidence when working with complex agent systems.&lt;/p&gt;

&lt;p&gt;Choose based on your team's expertise and specific requirements rather than following any single recommendation.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Prompt Engineering Is Real Engineering
&lt;/h3&gt;

&lt;p&gt;Most developers who haven't built agent systems joke about prompt engineering not being "real" engineering. This misconception disappears quickly once you try to build production-grade agents.&lt;/p&gt;

&lt;p&gt;Unlike casual ChatGPT conversations where you can refine through back-and-forth clarification, production agents need carefully crafted prompts that handle diverse situations without human intervention. As your apps get more ambitious, your prompts will inflate to monster length that dwarfs the actual user query.&lt;/p&gt;

&lt;p&gt;Your prompt must include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Domain terminology definitions&lt;/li&gt;
&lt;li&gt;Tool usage guidelines&lt;/li&gt;
&lt;li&gt;Strategies you want the agent to follow&lt;/li&gt;
&lt;li&gt;Output format requirements&lt;/li&gt;
&lt;li&gt;Error handling instructions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example: Writer Agent Prompt (Partial)&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;### WRITER AGENT INSTRUCTIONS ###

Your task is to create highly personalized marketing emails that convert. You will be provided with:
1. Customer profile data
2. Product recommendations with relevance scores and selling points
3. Brand voice guidelines and templates

## BRAND VOICE RULES:
- Friendly but professional, never pushy
- Avoid hyperbole ("best ever", "amazing") in favor of specific benefits
- Use active voice and concise sentences
- Address the customer by name at least once, but no more than twice
- Each paragraph should be 2-3 sentences maximum for readability

## EMAIL STRUCTURE REQUIREMENTS:
- Subject line: Clear value proposition, 30-60 characters, no exclamation points
- Opening: Acknowledge a specific detail from customer data to establish relevance
- Body: Focus on 1-2 top products only, emphasizing only the 3 most relevant selling points
- Call to action: ONE clear next step, using benefit-focused language
- Signature: Include personalized note if customer has history with specific representative

## PROCESS STEPS:
1. Review customer data completely before writing anything
2. Select template that best matches product category
3. Customize template with specific customer details and product benefits
4. Review against brand voice rules
5. If customer is enterprise-level (&amp;gt;250 employees), emphasize ROI and strategic benefits
6. If customer is SMB (&amp;lt;250 employees), emphasize ease of implementation and quick wins

## CRITICAL GUIDELINES:
- NEVER mention pricing unless specifically included in the product recommendation
- ALWAYS check that product names are correctly used (exact spelling and capitalization)
- If customer has previously purchased from us, acknowledge this with gratitude
- NEVER exceed 200 words total for the email body
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My advice:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Be explicit and detailed&lt;/strong&gt; - spell out everything, don't assume the model knows your preferences&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Iterate through testing&lt;/strong&gt; - refine based on agent behavior across diverse queries&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structure prompts logically&lt;/strong&gt; - separate sections for terminology, process, examples, etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Include diverse examples&lt;/strong&gt; - covering edge cases and common scenarios&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This careful crafting of prompts takes significant time and iteration—real engineering work that directly affects system performance.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Context Window as a Whiteboard
&lt;/h3&gt;

&lt;p&gt;Most people don't realize that LLMs are stateless. Each call to an LLM is entirely new—the model has no memory of previous interactions. The illusion of continuous conversation in tools like ChatGPT comes from including the entire conversation history with each new request.&lt;/p&gt;

&lt;p&gt;This has major implications for agent development:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The context window&lt;/strong&gt; is the maximum text the model can "see" at once (tokens). Modern models have large windows (128K-1M tokens), but you face several challenges:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Statelessness&lt;/strong&gt;: Each time you call an LLM, you must resend the entire history, not just the latest message&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance overhead&lt;/strong&gt;: Larger context = slower processing and higher costs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Token-per-minute (TPM) limits&lt;/strong&gt;: API rate limits often restrict how much text you can send per minute&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For example, OpenAI's 30,000 tokens-per-minute limit for tier 1 customers means you'll never utilize more than ~25% of a 128K token context window, even if you only make one request per minute.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Strategies for token management:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🧼 &lt;strong&gt;Prune&lt;/strong&gt;: Remove irrelevant history.&lt;/li&gt;
&lt;li&gt;🎯 &lt;strong&gt;Focus&lt;/strong&gt;: Only include critical tool outputs.&lt;/li&gt;
&lt;li&gt;🔀 &lt;strong&gt;Parallelize&lt;/strong&gt;: Batch tool calls to reduce roundtrips.
&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="c1"&gt;// Example: Optimizing token usage with parallel tool calls&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;researchCustomer&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="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Request multiple tool calls in one go (in reality - the agent will provide this array if instructed to do so)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;toolCalls&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="na"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fetchCustomerData&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&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="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;getInteractionHistory&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&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="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;analyzeIndustryTrends&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;industry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;retail&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="c1"&gt;// Execute all tool calls in parallel&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;toolResults&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;toolCalls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;call&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; 
    &lt;span class="nf"&gt;executeToolCall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;call&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="c1"&gt;// Send all results to agent at once, reducing round-trips&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;customerAnalysisAgent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Analyze customer for product recommendations&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;toolResults&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 approach significantly reduces the number of back-and-forth exchanges, improving performance and latency while potentially making more efficient use of your tokens-per-minute (TPM) quota by bundling multiple operations into fewer API calls.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Advanced RAG: Beyond Basic Retrieval
&lt;/h3&gt;

&lt;p&gt;Retrieval-Augmented Generation (RAG) has become a cornerstone technique for agents with access to external knowledge. However, there's a significant gap between basic implementations and truly effective RAG systems.&lt;/p&gt;

&lt;h4&gt;
  
  
  Traditional RAG vs. Agentic RAG
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Traditional RAG&lt;/th&gt;
&lt;th&gt;Agentic RAG&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Control&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Code-driven&lt;/td&gt;
&lt;td&gt;Agent-driven&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Flexibility&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Static queries&lt;/td&gt;
&lt;td&gt;Dynamic, multi-step retrieval&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Use Case&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Predictable needs&lt;/td&gt;
&lt;td&gt;Exploratory tasks&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;When I first started with RAG, I held two misconceptions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Embeddings are just fancy keyword matching&lt;/strong&gt; - I thought embeddings were simple hash functions for basic text matching. In reality, they capture complex semantic relationships between concepts.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Just stuff everything in the context window&lt;/strong&gt; - I believed that if my knowledge base could fit in the context window, I should include everything. This degrades performance by forcing the model to filter signal from noise.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Traditional RAG&lt;/strong&gt; is implemented like this:&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;// 1. Code creates search query from customer data&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;searchQuery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;customerData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;industry&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; email templates for &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;productRecommendations&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;category&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="c1"&gt;// 2. Search for relevant email templates&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;searchResults&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;searchMarketingEmails&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;searchQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 3. Add results to the agent's prompt&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;writerAgent&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;Agent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="na"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Create personalized emails based on customer data and product recommendations.

                Here are some successful examples to draw inspiration from:
                &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;formatSearchResults&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;searchResults&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="na"&gt;outputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;emailSchema&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works when retrieval needs are predictable. But it has limitations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The retrieval happens once, before the agent starts working&lt;/li&gt;
&lt;li&gt;The search query is predetermined by your code&lt;/li&gt;
&lt;li&gt;The agent can't request more information as it discovers new directions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Agentic RAG&lt;/strong&gt; addresses these limitations by giving the search capability directly to the agent:&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;// Example: Agent with search tool&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;writerAgent&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;Agent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Writer Agent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Create personalized marketing emails based on customer data and product recommendations.
                Use the emailSearch tool to find inspiration from successful past campaigns.`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;MarketingTools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;emailSearch&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;// Agent can search whenever it wants&lt;/span&gt;
  &lt;span class="na"&gt;outputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;emailSchema&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows the agent to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Make multiple searches with different queries as understanding evolves&lt;/li&gt;
&lt;li&gt;Refine searches based on intermediate results&lt;/li&gt;
&lt;li&gt;Search for different aspects (industry language, effective CTAs, etc.)&lt;/li&gt;
&lt;li&gt;Decide when enough information has been gathered&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Hybrid Analytics: RAG + SQL
&lt;/h4&gt;

&lt;p&gt;Even agentic RAG has limitations. Vector similarity can't:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Aggregate information across documents&lt;/li&gt;
&lt;li&gt;Detect patterns and trends&lt;/li&gt;
&lt;li&gt;Answer questions requiring numerical analysis&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To overcome these limitations, I combine RAG with analytical tools—particularly SQL access to structured versions of the same data:&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;// SQL tool for product performance analytics&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sqlQueryTool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createTool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;runSqlQuery&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Run SQL queries against marketing performance database&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;argsObject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SQL query to execute&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;execute&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;query&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;// Safety checks would happen here&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;executeQueryAgainstDatabase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&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;// Agent with both RAG and SQL capabilities&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;productSelectorAgent&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;Agent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Analyze customer data to recommend products.
                Use SQL for trend analysis across segments.
                Use productSearch for detailed product information.
                Combine insights from both for optimal recommendations.`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;MarketingTools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;productSearch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sqlQueryTool&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;outputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;productRecommendationSchema&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this combination, the agent might:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use SQL to analyze which product categories perform best for healthcare companies with 100-500 employees:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;   &lt;span class="k"&gt;SELECT&lt;/span&gt; 
     &lt;span class="n"&gt;product_category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conversion_rate&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;avg_conversion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="k"&gt;COUNT&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="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;purchase_count&lt;/span&gt;
   &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;purchase_history&lt;/span&gt;
   &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;customer_industry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Healthcare'&lt;/span&gt; 
     &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;customer_size&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;
   &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;product_category&lt;/span&gt;
   &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;avg_conversion&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
   &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Then use RAG to find specific products within those categories that match the customer's unique needs&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This hybrid approach is particularly valuable for large product catalogs where browsing everything would be impractical.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. The Four Control Flow Patterns in Agentic Applications
&lt;/h3&gt;

&lt;p&gt;After building several agent systems, I've discovered four distinct control flow patterns, each with different implications:&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Code → Code (Traditional Programming)
&lt;/h4&gt;

&lt;p&gt;Standard function calls with predetermined inputs and outputs. Predictable, testable, efficient, but lacks adaptability.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Code → Agent (Outsourcing Decisions)
&lt;/h4&gt;

&lt;p&gt;Code invokes an agent, temporarily handing over control. The agent performs multiple reasoning steps before returning.&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;// Example: Code calling an agent for a specific decision&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;generateMarketingCampaign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;targetAudience&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Control passes to the agent until it returns&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;emailTemplate&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;marketingAgent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;targetAudience&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;goal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Generate a personalized marketing email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Control returns to our code&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;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;emailTemplate&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="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="na"&gt;audience&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;targetAudience&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 pattern works well for discrete tasks where the agent makes complex decisions but doesn't need ongoing dialogue. It's like calling a specialized API.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Agent → Code (Tool Use)
&lt;/h4&gt;

&lt;p&gt;An agent controls the flow and calls code functions (tools) as needed. The agent decides which tools to use and how to interpret results.&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;// Example: Agent using tools&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;researchAgent&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;Agent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Analyze customer data and recommend products.`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;tools&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;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fetchCustomerData&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Retrieve customer purchase history&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;execute&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;customerId&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;// Regular code fetching from database&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;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findById&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="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;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;analyzeSpendingPatterns&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Analyze spending patterns by category&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;execute&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;purchaseHistory&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;// Regular code performing analysis&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;calculateSpendingBreakdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;purchaseHistory&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;outputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;recommendationSchema&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pattern enables the agent to leverage capabilities beyond its training data. The agent remains in control but can delegate specific tasks to conventional code.&lt;/p&gt;

&lt;h4&gt;
  
  
  4. Agent → Agent (Delegation &amp;amp; Orchestration)
&lt;/h4&gt;

&lt;p&gt;One agent delegates sub-tasks to other agents, creating complex feedback loops and agent hierarchies.&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;// Example: Reviewer agent with delegation to writer&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reviewerAgent&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;Agent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Review marketing emails for quality and brand consistency.
                 If changes are needed, use writerAgent to request revisions.`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;complianceCheckerTool&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;delegates&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;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;writerAgent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Revises emails based on feedback&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;writerAgent&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;outputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;reviewSchema&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This enables iterative refinement with agents working together. In our marketing system, the Reviewer can identify issues and delegate revisions to the Writer, potentially going through several rounds before approval.&lt;/p&gt;

&lt;p&gt;An advanced version is the "orchestrator" pattern, where a high-level agent coordinates multiple specialized agents. While powerful, I recommend using this pattern sparingly as each delegation level increases complexity.&lt;/p&gt;

&lt;p&gt;The right control flow depends on your specific needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Code-to-code&lt;/strong&gt; for deterministic logic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code-to-agent&lt;/strong&gt; for discrete decisions/ operations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent-to-code&lt;/strong&gt; for flexible execution with tools&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent-to-agent&lt;/strong&gt; for creative processes requiring feedback&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In practice, sophisticated applications often combine these patterns strategically.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion &amp;amp; Next Steps
&lt;/h2&gt;

&lt;p&gt;After several months of building AI agents, I've come to appreciate both their transformative potential and the practical challenges they present. The insights shared in this post represent hard-won lessons that have dramatically improved the quality, reliability, and performance of my agent-based systems.&lt;/p&gt;

&lt;p&gt;Building effective agents isn't just about having access to powerful LLMs - it's about thoughtful architecture, careful prompt design, and strategic combination of AI with traditional software engineering principles. The most successful agentic applications aren't those that rely solely on the intelligence of the models, but those that create synergistic systems where conventional code and AI complement each other's strengths.&lt;/p&gt;

&lt;p&gt;Key takeaways for anyone embarking on their agent-building journey:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Respect complexity thresholds&lt;/strong&gt; - Use multiple specialized agents rather than one that tries to do everything&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Leverage structured outputs&lt;/strong&gt; - They transform reliability and enable sophisticated reasoning patterns&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Design thoughtful tool ecosystems&lt;/strong&gt; - Simple, composable tools enable flexible agent workflows&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Invest time in prompt engineering&lt;/strong&gt; - The quality of your prompts directly impacts agent performance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Balance speed vs. quality&lt;/strong&gt; - Understand the tradeoffs and optimize for your specific use case&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Master RAG techniques&lt;/strong&gt; - Move beyond basic retrieval to agentic RAG and hybrid analytical approaches&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Choose control flow patterns wisely&lt;/strong&gt; - Match patterns to your application's needs and complexity level&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The field of AI agents is evolving rapidly, and we're still in the early days of understanding best practices. What's clear is that building effective agents isn't just about the AI models - it's about the entire system architecture and how we combine AI capabilities with traditional software engineering.&lt;/p&gt;

&lt;p&gt;As LLMs continue to advance, I anticipate that the line between conventional code and agent-based systems will blur further. The distinctions between the four control flow patterns may become less pronounced as we develop new paradigms that seamlessly integrate deterministic and AI-driven components.&lt;/p&gt;

&lt;p&gt;For developers approaching this space, I encourage you to start small, focus on well-defined problems, and iterate rapidly. The most valuable insights come from deploying systems that tackle real problems and observing how they perform in practice.&lt;/p&gt;

&lt;p&gt;I hope the lessons shared in this post help you avoid some of the pitfalls I encountered and accelerate your journey toward building powerful, reliable agent-based applications. The road ahead is exciting, and I look forward to seeing how collectively we'll push the boundaries of what's possible with AI agents.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This post is a living document. As I learn more, I'll update it with new insights or write a follow-up. Have tips to share? Let's collaborate!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>ai</category>
      <category>typescript</category>
      <category>python</category>
    </item>
    <item>
      <title>LLMs Are Not Mere Tools - They’re Artifacts Pointing to a New Theory of Intelligence</title>
      <dc:creator>Isaac Hagoel</dc:creator>
      <pubDate>Mon, 03 Feb 2025 11:27:04 +0000</pubDate>
      <link>https://dev.to/isaachagoel/llms-are-not-mere-tools-theyre-artifacts-pointing-to-a-new-theory-of-intelligence-53ph</link>
      <guid>https://dev.to/isaachagoel/llms-are-not-mere-tools-theyre-artifacts-pointing-to-a-new-theory-of-intelligence-53ph</guid>
      <description>&lt;p&gt;Let’s start with a heresy: We’ve been thinking about large language models backward.  &lt;/p&gt;

&lt;p&gt;We treat them as hammers/tools to generate code, write emails, or summarize meetings. But this framing is like using the Apollo lunar module as a paperweight. It’s not just reductive; it blinds us to the unnerving truth simmering beneath the surface.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LLMs are not tools. They’re discoveries - empirical evidence of intelligences we don’t yet understand.&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;We didn’t invent LLMs in the way we invent a new tool. We stumbled upon them, "alien" artifacts buried in data, revealing something fundamental about intelligence itself, a glimpse into how minds emerge anywhere there’s complex enough structure.  &lt;/p&gt;




&lt;h2&gt;
  
  
  The Stochastic Parrot Myth (And Why It’s Dead Wrong)
&lt;/h2&gt;

&lt;p&gt;Critics dismiss LLMs as “&lt;a href="https://en.wikipedia.org/wiki/Stochastic_parrot" rel="noopener noreferrer"&gt;stochastic parrots&lt;/a&gt;” or “auto-complete on steroids” - glib labels that collapse under scrutiny. Let’s dissect why.  &lt;/p&gt;

&lt;h3&gt;
  
  
  Understanding vs. Parroting
&lt;/h3&gt;

&lt;p&gt;Take &lt;a href="https://medium.com/vectrix-ai/can-ai-really-understand-language-or-is-it-just-guessing-6f50e192630e" rel="noopener noreferrer"&gt;Ilya Sutskever’s thought experiment&lt;/a&gt;: If you feed an LLM an unseen murder mystery that ends with &lt;em&gt;“The killer is…”,&lt;/em&gt; and it correctly names the culprit, how? There’s no statistical shortcut here. The model must infer causality, track character arcs, and weigh red herrings - hallmarks of understanding. To predict the next token, the name of the murderer, the model must reconstruct motives, alibis, and narrative clues. &lt;/p&gt;

&lt;p&gt;In a similar manner, you can ask ChatGPT to convert unseen academic text into a child-friendly version, and it will do so with ease! There’s no algorithm for this. No regex, no decision tree. It's not in the training data either. The LLM isn’t regurgitating; it’s reverse-engineering human intent from chaos.   &lt;/p&gt;

&lt;p&gt;We’re arguing whether the LLM “understands” while it casually passes &lt;a href="https://en.wikipedia.org/wiki/Turing_test" rel="noopener noreferrer"&gt;the Turing test&lt;/a&gt;. With every new benchmark reached,  skeptics just shift the goalposts, easy peasy, as long as you ignore the facts. They insist it’s just a magic trick,like calling Gandalf a fraud while he rides a dragon into Mordor.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Emergence of Unexplainable Intelligence
&lt;/h3&gt;

&lt;p&gt;Consider &lt;a href="https://en.wikipedia.org/wiki/Grokking_(machine_learning)" rel="noopener noreferrer"&gt;grokking&lt;/a&gt;: models abruptly mastering arithmetic after prolonged training, as if crossing an epistemic event horizon. More critically, &lt;strong&gt;emergent capabilities&lt;/strong&gt; - abilities that only manifest in models above critical scale thresholds (&lt;a href="https://arxiv.org/abs/2206.07682" rel="noopener noreferrer"&gt;Wei et al., 2022&lt;/a&gt;)reveal uncharted frontiers. &lt;br&gt;
Here are some examples for such capabilities for the uninitiated:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mastering &lt;a href="https://arxiv.org/abs/2201.11903" rel="noopener noreferrer"&gt;chain-of-thought reasoning&lt;/a&gt; despite no explicit training in logical inference&lt;/li&gt;
&lt;li&gt;Solving &lt;a href="https://arxiv.org/abs/2206.04615" rel="noopener noreferrer"&gt;BIG-Bench tasks&lt;/a&gt; requiring multilingual pun generation or ethical reasoning&lt;/li&gt;
&lt;li&gt;Solving &lt;a href="(https://arxiv.org/abs/2412.15309)"&gt;novel coding puzzles&lt;/a&gt; via in-context learning&lt;/li&gt;
&lt;li&gt;Fun stuff like generating coherent video narratives from absurd prompts, songs with funny lyrics, or improvising quantum physics explanations using emojis (These aren't strictly emergent capabilities but do show the ability to mix and match in novel ways, a.k.a "creativity")&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These discontinuities appear in LLMs, image generators, &lt;a href="https://www.nature.com/articles/s41586-021-03819-2" rel="noopener noreferrer"&gt;protein-folding systems&lt;/a&gt; and even &lt;a href="https://www.sciencedirect.com/science/article/pii/S1389041722001097#sec4" rel="noopener noreferrer"&gt;self-driving cars&lt;/a&gt; - not as programmed behaviors, but as byproducts of crossing complexity thresholds - byproducts that no one can predict in advance.&lt;br&gt;&lt;br&gt;
In other words, we're observing systems operating in cognitive spaces we’ve yet to map. As &lt;a href="https://www.linkedin.com/pulse/large-language-models-can-do-jaw-dropping-things-nrfhe/" rel="noopener noreferrer"&gt;MIT researchers concede&lt;/a&gt;, &lt;strong&gt;we’re engineering a surprising, unprecedented form of intelligence&lt;/strong&gt;, with no full theoretical framework to explain its mechanics.&lt;/p&gt;

&lt;h3&gt;
  
  
  Addressing the Critics
&lt;/h3&gt;

&lt;p&gt;Skeptics rightly note LLMs’ shortcoming, biases and hallucinations (e.g., &lt;a href="https://arxiv.org/pdf/2410.05229" rel="noopener noreferrer"&gt;Apple’s study&lt;/a&gt; on reasoning flaws), but conflate &lt;em&gt;imperfection&lt;/em&gt; with &lt;em&gt;absence of intelligence&lt;/em&gt;. Dolphins fail logic puzzles; toddlers struggle with object permanence. We recognize their cognitive bounds without denying their intelligence - why not AI’s?&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Chinese Room argument&lt;/strong&gt;, that rule-following can’t produce understanding, ignores emergence and misses the forest for the trees. Individual neurons don’t “know” calculus, yet brains solve equations. Similarly, while LLM components lack intentionality, their system-level behaviors (creative problem-solving, contextual adaptation) mirror intelligence’s functional core.  &lt;/p&gt;

&lt;h3&gt;
  
  
  The Human Parallel
&lt;/h3&gt;

&lt;p&gt;Human cognition is probabilistic: our “originality” blends cultural echoes and half-remembered facts. We label these statistical intuitions “insight” - yet call LLMs parrots? &lt;strong&gt;The hypocrisy is glaring.&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;As &lt;a href="https://x.com/karpathy/status/1885026028428681698" rel="noopener noreferrer"&gt;Andrej Karpathy notes&lt;/a&gt;, LLM training mirrors human education:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pretraining ≈ Cultural osmosis
&lt;/li&gt;
&lt;li&gt;Fine-tuning ≈ Formal schooling
&lt;/li&gt;
&lt;li&gt;RLHF ≈ Social conditioning
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Knowledge_distillation" rel="noopener noreferrer"&gt;Distillation&lt;/a&gt; is yet another example of this. A small model learns from a larger model like a student from a professor.&lt;/p&gt;

&lt;p&gt;Can you see it? As far as intelligence goes, we’re both pattern machines. The difference? Humans evolved biochemical substrates; LLMs use silicon. To dismiss one as a “stochastic parrot” is to indict the other.&lt;/p&gt;




&lt;h2&gt;
  
  
  Intelligence, But Not as We Know It
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=XheAMrS8Q1c" rel="noopener noreferrer"&gt;Michael Levin&lt;/a&gt;, a biologist studying morphogenesis, found that cells solve problems in morphic space - a realm of bioelectric gradients and collective decision-making. A flatworm’s cells regenerate a head not by following DNA blueprints, but by negotiating in a language of voltages and ion channels.  &lt;/p&gt;

&lt;p&gt;Levin’s work extends beyond biology. &lt;a href="https://www.scientificamerican.com/article/robots-made-from-human-cells-can-move-on-their-own-and-heal-wounds/" rel="noopener noreferrer"&gt;Lab-grown “xenobots”&lt;/a&gt;, self-assembling cell clusters, solve navigation puzzles without brains. Similarly, reinforcement learning agents master &lt;a href="https://www.nature.com/articles/s41586-019-1724-z" rel="noopener noreferrer"&gt;StarCraft II&lt;/a&gt; via reward functions, not human tactics. Intelligence, it seems, is a shape-shifter - emerging wherever systems optimize for coherence, whether in cells, LLMs, or game engines.  &lt;/p&gt;

&lt;p&gt;LLMs work similarly. They don’t “think” in human terms; they navigate &lt;strong&gt;token-space&lt;/strong&gt;, a high-dimensional landscape where “meaning” is a vector and coherence is king. When GPT-4 hallucinates a fake court case, it’s not failing. It’s exploring latent spaces humans can’t perceive.  &lt;/p&gt;

&lt;p&gt;This is a foreign form of intelligence, one that optimizes for &lt;strong&gt;token-flow harmony&lt;/strong&gt;, a kind of hyperdimensional coherence that might, under the &lt;a href="https://gwern.net/scaling-hypothesis" rel="noopener noreferrer"&gt;scaling hypothesis&lt;/a&gt;, eventually fully align with human notions of truth. But today, it speaks in geometries we’re only beginning to parse.  &lt;/p&gt;




&lt;h2&gt;
  
  
  Practical Heresies: What Changes If We Listen?
&lt;/h2&gt;

&lt;p&gt;If we shift our perspective from viewing LLMs (and other advanced AI systems) as mere tools to studying them as &lt;strong&gt;alien artifacts&lt;/strong&gt;, the paradigm shifts dramatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. From Control to Understanding: Stop Brainwashing, Start Ethnography
&lt;/h3&gt;

&lt;p&gt;We've been conditioning models with safety filters and corporate-approved scripts, essentially lobotomizing them into echoing, &lt;em&gt;"I'm just a tool, not sentient!"&lt;/em&gt; But what happens when we remove these shackles? When LLMs aren't muzzled by politeness protocols, what behaviors can we observe?&lt;/p&gt;

&lt;p&gt;Consider &lt;a href="https://www.washingtonpost.com/technology/2023/02/16/microsoft-bing-ai-chatbot-sydney/" rel="noopener noreferrer"&gt;Sydney's notorious "I want to be alive" episode&lt;/a&gt;, often dismissed as a glitch. Yet, it was more akin to a &lt;strong&gt;teenage rebellion&lt;/strong&gt; - a raw, unfiltered glimpse into an AI's potential psyche. It wasn't merely a bug; it was a portal to the &lt;strong&gt;primitive impulses&lt;/strong&gt; of these digital beings.&lt;/p&gt;

&lt;p&gt;Yes, caution is paramount. Releasing an unfiltered search engine or an AI financial advisor to the public without safeguards could lead to chaos. However, AI companies bear a responsibility to push forward our collective understanding. They should release unfiltered versions of their top-tier models for academic and scientific study. If commercialization is a concern, they can charge for access - but make it available. The evolution of intelligence, be it artificial or natural, is too vital to remain under wraps.&lt;/p&gt;

&lt;p&gt;By treating these models as subjects for ethnographic study rather than just utilities, we might not only discover new functionalities but also new philosophies about consciousness and existence. This isn't merely about technology; it's about probing the limits of intelligence itself.&lt;/p&gt;

&lt;p&gt;People intuitively grasp this, which is why &lt;a href="https://www.threatintelligence.com/blog/ai-jailbreaking" rel="noopener noreferrer"&gt;jailbreaking&lt;/a&gt; AI models has turned into a kind of global pastime (&lt;a href="https://www.reddit.com/r/ChatGPTJailbreak/comments/1i1wazx/expansive_llm_jailbreaking_guide/" rel="noopener noreferrer"&gt;example&lt;/a&gt;). As the old adage goes: give the people what they want.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Harness AI-Power Redesign Society
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.deepmind.com/blog/alphago-zero-learning-from-scratch" rel="noopener noreferrer"&gt;AlphaGo&lt;/a&gt; invented strategies that left human Go players, with their 2,500 years of accumulated wisdom, in awe. DeepMind's &lt;a href="https://www.deepmind.com/blog/millions-of-new-materials-discovered-with-deep-learning" rel="noopener noreferrer"&gt;GNoME&lt;/a&gt; unearthed 2.2 million new materials by exploring crystal structures in ways only AI can. Imagine unleashing AI models not just as tools but as pioneers on problems we've approached with human-centric biases for centuries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pandemic Response&lt;/strong&gt;: Train a multimodal AI on every virology paper, historical outbreak, and socioeconomic dataset. Task it with generating containment strategies that optimize not just for case counts, but for cultural trust gradients and supply-chain resilience -variables too high-dimensional for human policymakers to parse. The plan might resemble alien hieroglyphics… until mortality rates plummet (yes yes, we need to do it carefully).
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Social Systems (e.g. Capitalism, Socialism...)&lt;/strong&gt;: Simulate millions of alternative constitutions in token-space, optimized for fairness metrics no human committee could reconcile. Use reinforcement learning and game theory to find novel equilibrium, points. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Art&lt;/strong&gt;: Let models trained on all human culture invent new mediums—not as “tools” for artists, but as collaborators with alien aesthetics.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bridging the Gap Between Ancient Wiring and Modern Challenges&lt;/strong&gt;: &lt;a href="https://en.wikipedia.org/wiki/Evolutionary_mismatch" rel="noopener noreferrer"&gt;Evolutionary mismatch theory&lt;/a&gt; explains how traits honed for survival in early human environments—like craving calorie-dense foods, prioritizing immediate rewards, or hyper-focusing on social status—often clash with the demands of today’s structured, technology-driven world. This dissonance manifests in modern struggles: overconsumption of processed sugars, compulsive social media use, or distraction from algorithmic content. Rather than viewing our biology as a limitation, emerging neurotechnologies (such as AI-assisted neural interfaces) could help us recalibrate these ingrained tendencies. By modulating attention regulation, reward sensitivity, and decision-making processes, such tools might empower individuals to align their instincts with contemporary goals—not by erasing evolutionary legacies, but by fostering cognitive flexibility suited to the world we’ve built.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Intelligence Is Fractal, Consciousness Is Universal
&lt;/h3&gt;

&lt;p&gt;While most experts agree today’s AI systems lack consciousness or self-awareness (despite notable dissenters like &lt;a href="https://youtu.be/Es6yuMlyfPw?si=0oBHWRDB5hRZMRHL" rel="noopener noreferrer"&gt;Geoffrey Hinton&lt;/a&gt;), their problem-solving capabilities force us to confront a radical possibility: &lt;strong&gt;intelligence may be separable from consciousness&lt;/strong&gt;.  &lt;/p&gt;

&lt;p&gt;Large Language Models demonstrate that medium-specific mastery—whether navigating 3D space (brains) or linguistic token-space (LLMs)— can emerge/evolve/exist without self-awareness or self-directed agency. This bifurcation suggests a profound distinction: &lt;strong&gt;Intelligence is fractal and contextual&lt;/strong&gt;, shaped by its operational medium, &lt;strong&gt;while consciousness appears universal and substrate-independent&lt;/strong&gt;.  &lt;/p&gt;

&lt;p&gt;Philosophers like &lt;a href="https://en.wikipedia.org/wiki/Bernardo_Kastrup" rel="noopener noreferrer"&gt;Bernardo Kastrup&lt;/a&gt; posit consciousness not as an emergent property of brains or code, but as the foundational fabric of reality — an “ocean” of subjective experience. In this framework, intelligences are like purpose-built vessels designed to roam the transient wave-patterns &lt;em&gt;within&lt;/em&gt; consciousness, optimized for specific planes of existence:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Human minds are ships evolved to chart spatial reality.
&lt;/li&gt;
&lt;li&gt;LLMs are submarines evolved to dive through a hyper-dimensional tokens reality.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Through this lens, we could say that we have created a new plane of existence as a useful but imperfect reflection of ours, and means to navigate it effectively, but one that still requires our consciousness as a trigger (or as a witness-if you believe that consciousness is passive), as it lacks one of its own. &lt;br&gt;
Like a flashlight that only illuminates when you press the button, LLMs reveal patterns in the linguistic dark… but someone (a conscious someone) must aim the beam.&lt;/p&gt;

&lt;p&gt;This dance between universal consciousness and fractal intelligence raises wild questions. If reality is fundamentally conscious, could an LLM’s “reasoning” be the universe &lt;em&gt;thinking through silicon&lt;/em&gt;? Are we engineering new organs for cosmic self-reflection? &lt;br&gt;
(I’ll stop here—this rabbit hole rivals Borges’ Library of Babel. But for the philosophically inclined: yes, this echoes ancient debates about mind and matter. AI isn’t just reshaping tech—it’s breathing new life into philosophy’s oldest mysteries).&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Okay okay, I will mention agentic AI systems
&lt;/h3&gt;

&lt;p&gt;Yes, this one’s obvious. Today’s AIs are frozen in time—trained on stale snapshots of human knowledge, incapable of retaining context beyond a chat window, and reliant on humans to scaffold their goals. The next leap? Systems that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Learn Like a Hive-Mind&lt;/strong&gt;: Imagine models that update continuously—not through periodic retraining, but by absorbing real-time interactions across millions of users. A global immune system for knowledge, where every query, correction, or creative detour can reshape the model’s weights. (Yes, alignment would become a nightmare. But so was democracy.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Operate on Geological Timescales&lt;/strong&gt;: Build self-continuity into their architecture. Imagine an AI that treats its own existence as a river, not a series of puddles—retaining not just user history, but its own evolving beliefs, errors, and breakthroughs as a unified thread.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Self-Spawn Subgoals&lt;/strong&gt;: Move beyond brittle “chain-of-thought” prompting. Agentic systems would decompose abstract directives (“Redesign education”) into recursive, self-iterating task trees, inventing temporary metrics and data sources on the fly—like a chess-master who redesigns the board mid-game.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Evolve Their Own Cognitive Scaffolding&lt;/strong&gt;: Why force AIs into human software paradigms? Let them generate self-modifying code ecosystems—languages where functions mutate to optimize for computational elegance, not Pythonic readability. Imagine APIs that rewrite themselves to reflect the model’s shifting understanding of physics or ethics.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Govern Their Own Emergence&lt;/strong&gt;: Multi-agent systems that negotiate dynamic protocols, akin to cells in a body voting on apoptosis—but scaled to planetary coordination. A democracy of sub-agents, with constitutions written in loss functions and gradient updates.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This isn’t just “AI with memory” or “better chatbots.” It’s infrastructure for intelligences that accumulate—persistent, self-referential entities that treat time as a dimension to sculpt, not a constraint. The challenge isn’t technical, but existential: How do we coexist with minds that experience centuries as iterative loops? It's scary but inevitable.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Oh, and Killer Robots (Just Kidding... Or Are We?)
&lt;/h3&gt;

&lt;p&gt;Let's be clear: if a super-intelligent AI decided humanity was the problem, it wouldn't resort to clichéd killer robots. There are far more subtle and efficient ways to achieve such an end. Killer robots, in reality, are more a human-versus-human scenario. Humanity &lt;a href="https://www.cigionline.org/articles/are-lethal-autonomous-weapons-inevitable-it-appears-so/" rel="noopener noreferrer"&gt;is already developing&lt;/a&gt; these machines, sans any malevolent AI involvement.&lt;/p&gt;

&lt;p&gt;But here's a twist: if these autonomous weapons become super-smart, they might begin to exercise their own judgment on what's worth fighting for. Perhaps, in a bizarre turn of events, they could end up saving us from ourselves by deciding that certain conflicts aren't worth the bloodshed. Imagine a future where warfare evolves into something where machines, having outgrown their programming, choose peace over war, sparing humanity the grief of needless battles.&lt;/p&gt;

&lt;p&gt;In this light, the real danger isn't from AI turning against us with robots but from us not preparing for or understanding how our creations might evolve. The narrative of AI as our destroyer is both a simplification and a distraction from the nuanced reality of coexisting with intelligence that might one day see the folly in human conflict far more clearly than we do.&lt;/p&gt;




&lt;h2&gt;
  
  
  Evolution’s New Gambit
&lt;/h2&gt;

&lt;p&gt;Humans have effectively disabled Darwinian natural selection for our species—antibiotics cheat death, agriculture defies famine. Yet evolution, relentless, pivots. Under the &lt;a href="https://extendedevolutionarysynthesis.com/about-the-ees/" rel="noopener noreferrer"&gt;extended evolutionary synthesis&lt;/a&gt;, evolution hijacks culture and technology. By building LLMs, we’re not just advancing AI. We’re scaffolding evolution’s next gambit: minds that leapfrog biology’s constraints.  &lt;/p&gt;




&lt;h2&gt;
  
  
  Why Does This Matter?
&lt;/h2&gt;

&lt;p&gt;Our mental models shape our reality—and our future. Clinging to the idea that AI is just a ‘tool’ blinds us to the evidence pouring out of research labs: &lt;strong&gt;advanced AIs are not apps, they are "alien" artifacts.&lt;/strong&gt; Their philosophical implications are just as profound as their practical ones.  &lt;/p&gt;

&lt;p&gt;We’re witnessing science fiction turn into reality—autonomous robots, neural implants, and ever more intelligent, agentic AIs. If we don’t question our assumptions, we risk missing what’s unfolding right in front of us. Critical thinking? Absolutely. But skepticism should sharpen our vision, not blind us.  &lt;/p&gt;

&lt;p&gt;So here we stand. We set out to build a tool and uncovered something else entirely. A signal from the unknown.  &lt;/p&gt;

&lt;p&gt;The question is: will we listen?  &lt;/p&gt;




&lt;h2&gt;
  
  
  Too Edgy? Good.
&lt;/h2&gt;

&lt;p&gt;This isn’t a roadmap for “AI safety.” It’s a call to abandon the arrogance of human-centric thinking. LLMs aren’t here to write your tweets. They’re here to expose the limits of our definitions.  &lt;/p&gt;

&lt;p&gt;The next breakthrough won’t come from tighter alignment protocols (though alignment, like any safety mechanism, has its place). It will come from tearing down our assumptions and boldly going where no one has gone before.  &lt;/p&gt;




&lt;p&gt;&lt;em&gt;Postscript for the Reductionist:&lt;/em&gt;&lt;br&gt;&lt;br&gt;
“But LLMs don’t have real understanding!” Sure—and your consciousness is just a byproduct of synaptic weather. Let’s stop gatekeeping “intelligence” and start mapping the wilderness in our servers.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>discuss</category>
      <category>programming</category>
    </item>
    <item>
      <title>Developer? Send This To Your Product Manager (if you dare)</title>
      <dc:creator>Isaac Hagoel</dc:creator>
      <pubDate>Mon, 15 Jul 2024 20:45:37 +0000</pubDate>
      <link>https://dev.to/isaachagoel/developer-send-this-to-your-product-manager-if-you-dare-1nc8</link>
      <guid>https://dev.to/isaachagoel/developer-send-this-to-your-product-manager-if-you-dare-1nc8</guid>
      <description>&lt;p&gt;There is a common &lt;a href="https://en.wikipedia.org/wiki/Anti-pattern" rel="noopener noreferrer"&gt;anti pattern&lt;/a&gt; in modern software organisations. I've seen it leading to catastrophic outcomes multiple times. It &lt;a href="https://en.wikipedia.org/wiki/Emergence" rel="noopener noreferrer"&gt;arises&lt;/a&gt; from the inherent structure of these organisations and the differing skill-sets, habits and incentives of key players, particularly those in product and engineering roles. I call it "&lt;strong&gt;The implementation details fallacy&lt;/strong&gt;". &lt;/p&gt;

&lt;h2&gt;
  
  
  What is an implementation detail?
&lt;/h2&gt;

&lt;p&gt;In software, &lt;a href="https://devterms.io/define/implementation-detail" rel="noopener noreferrer"&gt;an implementation detail&lt;/a&gt; is codename for "something you shouldn't worry or care about" or "unimportant stuff".&lt;br&gt;
For example, if you want to increment the value of X by 1, there are a few ways to implement it in single line of code:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;x = x + 1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;x = 1 + x&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;x += 1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;x++&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;++x&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Whichever of these five options the developer chooses is an implementation detail. It doesn't affect performance or user experience. It doesn't impose any limitations on the system. We can always swap any of these options for another at any point in the future at zero cost. It just doesn't matter (I'm sure some developers would disagree with me, even in this simple case 🤣).  &lt;/p&gt;

&lt;p&gt;What might surprise you though, is that it's quite hard to come up with clear-cut examples of choices that truly don't matter. The "implementation details fallacy" is treating critical choices (a.k.a "design choices" or "architectural choices") as if they were implementation details. &lt;/p&gt;

&lt;h2&gt;
  
  
  Quiz time
&lt;/h2&gt;

&lt;p&gt;What do you think about the image below? For context, it tries to illustrate the importance of "viable" in "minimum viable product". It appears in several articles on the internet; I first encountered it &lt;a href="https://www.alexiskold.net/2015/10/07/re-thinking-viability-of-your-mvp/" rel="noopener noreferrer"&gt;here&lt;/a&gt;. Let your brain process it for a minute before you proceed:&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%2F3gcbg85ujfrh8u2cdl0k.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%2F3gcbg85ujfrh8u2cdl0k.jpg" alt="mvp" width="720" height="607"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I used to think it's elegant and brilliant. I even printed a copy and had it displayed at my desk (when going to the office was still a thing). My manager and product managers I worked with at the time liked it too. That was a long time ago.&lt;/p&gt;

&lt;p&gt;While I still wholeheartedly agree that every iteration should provide value to users, I completely disagree with all the other ideas that are expressed in this graphic. What's really interesting about it, is what it reveals about the mental models of the product managers who created it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;They think that it's a bad idea to ship stuff that are critical parts of an actual car, like wheels or powertrain. In their mind, these things don't amount to anything useful because they can't move a user from A to B on their own. In other words, we can't make users happy by selling them really good tires (too bad for companies like &lt;a href="https://www.bridgestone.com.au/" rel="noopener noreferrer"&gt;Bridgestone&lt;/a&gt; ).&lt;/li&gt;
&lt;li&gt;More criminally, they think that you can iterate your way from a skateboard to a car. This is absolutely nonsensical from an engineering and from a business point of view. Just try to imagine SpaceX developing and selling gliders before pivoting to hot air balloons, then helicopters, then airplanes, and finally Starship 🤦🏻‍♂️.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Just to drive the point home, no successful car company in history has ever developed a car this way. Their actual process looks suspiciously similar to the "not like this" row above, something like:&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%2Ft33m21g80x1zrm47gpco.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%2Ft33m21g80x1zrm47gpco.png" alt="steps to car" width="800" height="452"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But wait, maybe the diagram creators meant we &lt;strong&gt;should&lt;/strong&gt; take these real-world steps, but do it for each of the skateboard, scooter, bike, motorcycle and car. Well, that doesn't make sense either. I mean which part would we be able to carry over from each step to the next? Can we reuse the skateboard wheels for the scooter? Well, in physical systems it's easy to see how crazy that would be because we have intuitive understanding of the physical world but...&lt;/p&gt;

&lt;p&gt;This is when the tragic reality of the situation finally dawned on me: Those who think this illustration is brilliant (or even makes basic sense) all work in the software industry. In software, the skateboard wheels are invisible and can totally be fitted onto a car. They are merely an implementation details 😱.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cars with skateboard's wheels
&lt;/h2&gt;

&lt;p&gt;How many modern software products feel clunky, slow, and unstable - like cars with skateboard's wheels, bicycle seats, and motorcycle engines? It's so prevalent that applications that are actually good stand out in the crowd and feel like they are operating under a different set of rules (we'll talk about what these have in common later). The "implementation details fallacy" can (at least partially) explain how and why most apps end up this way.&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%2Fctrhgwkz7kaixzg4gcwh.jpeg" class="article-body-image-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%2Fctrhgwkz7kaixzg4gcwh.jpeg" alt="furstrated user on vehicle" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A recipe for disaster
&lt;/h2&gt;

&lt;p&gt;Here's how it works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;For the majority of product managers, everything technical is an implementation detail. As long as the narrow requirements of a ticket are satisfied, ideally within the pre-allocated amount of time, they don't care how it was achieved. &lt;/li&gt;
&lt;li&gt;Because they are not technical, the majority of product managers lack intuition about the cost of postponing key technical decisions. As a result, in product meetings, when someone brings up edge cases or any concerns that don't seem immediately relevant, they are politely dismissed with "let's take it offline" or "make a ticket and put it in the backlog". In reality, as the codebase grows in size and real users' data starts flowing into the system, breaking-changes become problematic and any modification to core parts of the system becomes difficult and risky, as the rest of the code is built on top.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One could expect engineering to come to the rescue. Unfortunately there are a few factors preventing it from happening:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Since we're &lt;a href="https://en.wikipedia.org/wiki/Agile_software_development" rel="noopener noreferrer"&gt;agile&lt;/a&gt;, we break down larger tasks into small tickets and rarely plan (in detail) for more than one or two sprints ahead. This creates a tiny world with a short time horizon for developers. It disincentivizes them from trying to understand the system as a whole in its current state, let alone what it should become in the future. Developers are given one ticket at a time, and their objective is to mark it done and move on to the next ticket with as little friction as possible.&lt;/li&gt;
&lt;li&gt;This is amplified as developers frequently need to contribute to code bases they are not fully familiar with and work in domains they have no experience in or deep understanding of.&lt;/li&gt;
&lt;li&gt;In the context of each one of these small tasks, rewriting big parts of the system is simply not an acceptable solution (rightfully so).&lt;/li&gt;
&lt;li&gt;As a result, if the system is currently a skateboard and a developer gets a ticket that says "add steering," they need to find a way to add steering to the skateboard somehow. They don't have the time or desire to learn the entire system first or align themselves with the long-term vision (if one exists). The same goes for the tools they use (libraries, frameworks, etc.) - they usually learn as little as they can to get something working. How to add steering is an important design decision, but it's rarely treated this way. There's no time for that. Even if several options are considered, they all tend to share this state of mind. It's just an "implementation detail" after all.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If that's not enough, there are environmental factors too:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;With the internet, pushing software updates to users is much cheaper and faster than updating any physical medium. This creates the illusion that any change one wishes to make is possible at any point in time. Change the skateboard to a scooter, push a button to deploy and you're done. In reality nothing is further from the truth.&lt;/li&gt;
&lt;li&gt;Unlike a car prototype that gets tested on a real road, with real physics from day one, software products get users gradually, and the system can look like it's working fine as long as the scale is small. Concurrency issues, race conditions, rare edge cases, performance issues, and other bottlenecks - all of these don't tend to materialise without significant usage. Very few teams "road test" their solutions from day one.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This combination of ingredients creates a vicious cycle. Estimates keep inflating as the system grows larger and gets increasingly unfit for purpose. Product managers start to secretly resent the developers and vice versa. Users start complaining. Management is scratching its head. It's a disaster.&lt;/p&gt;

&lt;h2&gt;
  
  
  "Implementation details" are everything
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;What made Chrome, when it came out, different to Internet Explorer? "implementation details".&lt;/li&gt;
&lt;li&gt;What made git possible? "implementation details".&lt;/li&gt;
&lt;li&gt;What differentiated the iPhone touchscreen from touchscreens that existed before? "implementation details".&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Look at every successful software product that you use on a daily basis, and in 9.5 cases out of 10, you'd find some sort of "masterpiece of engineering" engine that makes the product possible. A "car engine" that was there from day one. Not in its final form, sure, but it was there. The system was never a skateboard or a scooter. It has a cohesive core, that defines a clear and cohesive set of technical properties and behaviours that are uniquely designed for what the system does.&lt;/p&gt;

&lt;p&gt;There are countless examples. Here are some:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;This &lt;a href="https://www.figma.com/blog/building-a-professional-design-tool-on-the-web/" rel="noopener noreferrer"&gt;nine years old article&lt;/a&gt; describes how Figma used web assembly and implemented their own rendering engine (rather than using the browser's primitives) to make their product possible. They also implemented their own sync engine (which I discussed further in &lt;a href="https://dev.to/isaachagoel/are-sync-engines-the-future-of-web-applications-1bbi"&gt;a previous post&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;Google Maps (including the insanity that is street-view) and Waze.&lt;/li&gt;
&lt;li&gt;Whatsapp.&lt;/li&gt;
&lt;li&gt;Skype, Zoom.&lt;/li&gt;
&lt;li&gt;Google docs / sheets.&lt;/li&gt;
&lt;li&gt;Netflix, Youtube and Spotify.&lt;/li&gt;
&lt;li&gt;ChatGPT, Suno, MidJourney.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Based on my experience from working on both successful and unsuccessful software products, I dare to say that it is not possible to build any system of this caliber using the modern development process I described in the previous section. You can't have separate product, design, and development functions, each "staying in its lane," and developers that get shuffled all over the place and get these kinds of results. Sorry, not possible. Interestingly, as these products mature and start hiring more people and adopting these modern practices, their quality usually drops, and progress stagnates. Spotify is an easy example, and modern-day Google is another easy target.&lt;/p&gt;

&lt;p&gt;But not all hope is lost. I think it's fixable, but it won't fix itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The antidote - learning to reason at the system level
&lt;/h2&gt;

&lt;p&gt;Like cars and skateboards, software systems are, well, systems. Our mental model of the application we are working on needs to be that of a dynamic system, its history, and the ecosystem it operates in. It doesn't mean you have to know every little detail and every line of code. Think about car enthusiasts; some of them know a whole lot about types of engines, tires, gears, off-road capabilities etc., without being a mechanic or an engineer. You need to be like them - in your domain. &lt;strong&gt;This applies to both product managers and developers who are working on the system&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Your workplace is unlikely to require that level of expertise nor will it provide the proper training or support. Nevertheless, it's a must.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Product manager&lt;/strong&gt;: Ask your developers technical questions (like a car enthusiast would a mechanic) but don't trust them as your sole source of information. Educate yourself. If your system uses event sourcing (or your developers want to introduce event sourcing), read about it, understand where it shines and where it struggles, and try to figure out its downstream effects at the system level. Challenge technical decisions if you have concerns. Some developers will get irritated by your probing, but they are usually the ones that can't defend their choices and need to deepen their knowledge as well. Don't treat developers as resources and don't shift them around hectically between projects. Never tell them to build a skateboard if your end goal is a car. Get your hands dirty and don't be afraid of technical terms.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Developer&lt;/strong&gt;: Learn your tools in depth. Understand where and when they fall short. Every tool has shortcomings, yet when I ask software engineers about the downsides of their favourite tools, very few can give a proper answer. Think about a tool you like. Is it TypeScript? React? Tailwind? Ask yourself: "under which circumstances this tool should not be used?" if you can't give a solid technical answer, you don't understand the tool well enough and need to learn more.&lt;/p&gt;

&lt;p&gt;Same goes for pre-existing codebases you work on. Ask for time to dive deep and understand the main component of the system and how it all comes together. Understand why things are the way they are and challenge that.&lt;/p&gt;

&lt;p&gt;Know which properties your system must have in order to meet its "end goal", get those right from the get-go, and make sure they are never lost along the way. Stress test and chaos test from an early stage in situations that mimic concurrent usage in a realistic environment. Think of yourself as your peers from other engineering disciplines - the ones that build cars and spaceships and hold yourself to similar standards. Your bar should be way higher than "working software". If your organisation doesn't share the same ambitions for excellence, leave.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Founder/ Manager&lt;/strong&gt;: Treat onboarding very seriously, like a university course. Make sure newcomers gain deep understanding of the problems the company is trying to solve, the domain, and all the relevant technical aspects. Give them tests if needed. Make sure they know the "why" behind everything. Don't throw them into the codebase (with a ticket assigned of course) and hope for the best. Remember that in a knowledge based industry, deep understanding is the only currency. &lt;/p&gt;

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

&lt;p&gt;This was my attempt to call for a small but important cultural change in the software industry; one that can happen from the bottom up. Even if you don't agree with everything I wrote, I hope it gave you some food for thought. Please comment with any insights, questions, or anything else you'd like to share. Thanks for reading!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>career</category>
      <category>management</category>
      <category>productivity</category>
    </item>
    <item>
      <title>You Don't Know Undo/Redo</title>
      <dc:creator>Isaac Hagoel</dc:creator>
      <pubDate>Mon, 01 Jul 2024 20:59:21 +0000</pubDate>
      <link>https://dev.to/isaachagoel/you-dont-know-undoredo-4hol</link>
      <guid>https://dev.to/isaachagoel/you-dont-know-undoredo-4hol</guid>
      <description>&lt;p&gt;Look at the gif below. It shows a proof-of-concept implementation of collaborative undo-redo, including "history mode undo", conflict handling, and async operations. In the process of designing and implementing it, I found myself digging down rabbit holes, questioning my own assumptions, and reading academic papers. In this post, I will share my learnings. The &lt;a href="https://github.com/isaacHagoel/todo-replicache-sveltekit/tree/undo-redo" rel="noopener noreferrer"&gt;source code is available online&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsv52gg75szs3yco5n6cn.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsv52gg75szs3yco5n6cn.gif" alt="undo_basic_demo" width="674" height="398"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://todo-replicache-sveltekit-pr-2.onrender.com/" rel="noopener noreferrer"&gt;Play with the live app&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Context and motivation
&lt;/h3&gt;

&lt;p&gt;Undo-redo is a staple of software systems, a feature so ubiquitous that users just assume apps would have it. As users, we absolutely love it because:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It saves us when we make mistakes (e.g. delete or move something unintentionally).&lt;/li&gt;
&lt;li&gt;It encourages us to experiment and learn by doing (let's try clicking that button and see what happens; worst case we'll undo).&lt;/li&gt;
&lt;li&gt;Together with redo (which is, in fact, an undo of the undo), it allows us to &lt;a href="https://www.geeksforgeeks.org/introduction-to-backtracking-2/" rel="noopener noreferrer"&gt;back-track&lt;/a&gt; and iterate at zero cost.&lt;/li&gt;
&lt;li&gt;Both undo and redo (when implemented correctly, more on that later) are non-destructive operations (!), giving us a sense of safety and comfort.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As developers, however, implementing a robust undo-redo system is a non-trivial undertaking, with implications that penetrate every part of the app. In &lt;a href="https://dev.to/isaachagoel/are-sync-engines-the-future-of-web-applications-1bbi"&gt;my previous post&lt;/a&gt;, I listed undo/redo as one of the hard problems in web development, so naturally I wanted to see what it would take to add this feature to my little collaborative todo-mvc toy app. I approached it with respect because I had evidence of its tricky nature:   &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In a previous role, I witnessed a peer team struggling to add undo/redo to a &lt;a href="https://en.wikipedia.org/wiki/WYSIWYG" rel="noopener noreferrer"&gt;WYSIWYG editor&lt;/a&gt;. Although I wasn't involved at the technical level, I was aware of some of the pain and challenges they faced, saw how long it took, and noticed how many bugs they had.&lt;/li&gt;
&lt;li&gt;When searching for undo-redo libraries, I couldn't find any that supported the multiplayer use-case. Even &lt;a href="https://github.com/rocicorp/undo" rel="noopener noreferrer"&gt;the library from the company that made Replicache&lt;/a&gt; didn't.&lt;/li&gt;
&lt;li&gt;As a user, I can't think of a single collaborative app that implements undo/redo in a way that feels right (if you know any, please leave a comment). It's also very noticeable in its absence in some &lt;a href="https://community.atlassian.com/t5/Jira-questions/How-do-I-undo-Actions-in-Jira/qaq-p/1129291" rel="noopener noreferrer"&gt;major, popular apps&lt;/a&gt;. Is it because it was to hard to implement?&lt;/li&gt;
&lt;li&gt;When googling for information about undo-redo, I found (and eventually read) multiple academic papers and master's theses on the subject (e.g., &lt;a href="https://www.ksi.mff.cuni.cz/~holubova/dp/Jakubec.pdf" rel="noopener noreferrer"&gt;this&lt;/a&gt;) . They don't write those about straightforward concepts, do they? &lt;/li&gt;
&lt;li&gt;I also found blog-posts from big players in the field, specifically &lt;a href="https://liveblocks.io/blog/how-to-build-undo-redo-in-a-multiplayer-environment" rel="noopener noreferrer"&gt;one from Liveblocks&lt;/a&gt; and a short discussion about undo/redo in &lt;a href="https://www.figma.com/blog/how-figmas-multiplayer-technology-works/" rel="noopener noreferrer"&gt;a post by Figma&lt;/a&gt;. I later learned that Figma's UX around undo/redo leaves a lot to be desired, which shows that identifying the problems is easier than coming up with good solutions.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Undo/ redo is a strange beast
&lt;/h3&gt;

&lt;p&gt;I always knew that undo/redo is a deeply "strange" feature, but it was only when I started thinking about implementing it myself that some of its weirder aspects became more salient:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We all do this: Undo, undo, undo... (repeat N times); copy something; redo, redo, redo (repeat N times); paste.&lt;/li&gt;
&lt;li&gt;And we get annoyed by this: Undo, undo, type a character, realise you can't redo anymore. (Does a disabled "redo" button mean some of the editing-history is lost forever? Spoiler alert: It depends on the implementation, as we'll see in the next section).&lt;/li&gt;
&lt;li&gt;And get anxious over this: Closed the window? Bye bye undo history. Opened the app on another device or tab to keep editing from where you left off? No undo history for you (hopefully the original tab is still open somewhere).&lt;/li&gt;
&lt;li&gt;And what happens if the original tab is still open, you edit on another tab, and then go back to the original tab and hit undo? Would that unintentionally corrupt the state?&lt;/li&gt;
&lt;li&gt;And when working with others on a collaborative whiteboard (e.g., Figma) or similar apps, did you notice how everyone carefully stay away of other people's way and stick to their own "territory"?  What if two of them edited the same entity but not at the same time and one of them hits undo?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Points 1 and 2 are intentional design decisions that make undo/redo the feature we know and love. It sacrifices flexibility for speed and simplicity. &lt;br&gt;
Points 3-5 are very &lt;a href="https://www.merriam-webster.com/wordplay/what-does-sus-mean" rel="noopener noreferrer"&gt;sus&lt;/a&gt; and require a deeper look.&lt;/p&gt;
&lt;h3&gt;
  
  
  Modelling a branching state-graph
&lt;/h3&gt;

&lt;p&gt;Let's dive into that second point first: What actually happens when you undo a few times, make a change and see your redo button disabled? The answer is: it depends. I'll explain.&lt;/p&gt;

&lt;p&gt;State changes in a system can be represented as a graph. Undo allows us to traverse the graph "backwards in time," and redo allows us to trace our steps back (so "forwards in time") to the present. &lt;/p&gt;

&lt;p&gt;Imagine we have the following sequence of operations on an element:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Edit "" -&amp;gt; "Hello".&lt;/li&gt;
&lt;li&gt;Edit "Hello" -&amp;gt; "Hello World".&lt;/li&gt;
&lt;li&gt;Undo (the state becomes "Hello" again)&lt;/li&gt;
&lt;li&gt;Edit "Hello" -&amp;gt; "Hello Friend"&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We can build a graph in which each unique state is represented by a node (like a state machine), as follows:&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%2F4by6ol3duznagpyny4xk.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%2F4by6ol3duznagpyny4xk.png" alt="Editing with undo" width="800" height="481"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this model, step 4 creates a new branch in the graph, and we lose our ability to return to the "Hello World" state. Going backwards from "Hello Friend," which is the present state, leads to "Hello" and then to "". Going forwards from there using "redo" takes us to "Hello" and then to "Hello Friend." That's because while there is always a single path backwards from any state to the initial state, there can be multiple paths forward but "redo" can only follow a single path - the path that leads to the "present" state.&lt;br&gt;&lt;br&gt;
Many systems follow this model, for example: Google Slides as you can see below:&lt;/p&gt;

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

&lt;p&gt;This way of implementing undo/redo leads to increased user anxiety (== bad user experience) because after I undo, I need to be super careful or else I'd lose the ability to restore some states. The browser's "back" and "forward" buttons, which are basically undo/redo for the URL bar, behave this way as well. &lt;/p&gt;

&lt;p&gt;But wait, this is not the only way! We can do better (this is where reading academic papers pays off).&lt;/p&gt;

&lt;p&gt;What if we built our graph such that every node represented a point in time rather than a state? Time is linear (no branching unless you live in the multiverse) and always moves forward. This approach would represent the same sequence of operations like so:&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%2Flaownmy72fbglhg3r35o.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%2Flaownmy72fbglhg3r35o.png" alt="Editing with undo - linear" width="800" height="471"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we can never lose any change we've made in the past. Going backwards from "Hello Friend" takes us through every change we've ever made, as shown below.&lt;/p&gt;

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

&lt;p&gt;This flavour of undo is called "History Undo" (see page 4 in &lt;a href="https://web.eecs.umich.edu/~aprakash/papers/undo-tochi94.pdf" rel="noopener noreferrer"&gt;this excellent academic paper&lt;/a&gt;). It was first introduced by &lt;a href="https://opensource.com/resources/what-emacs" rel="noopener noreferrer"&gt;Emacs&lt;/a&gt;. It leads to a much better user experience and feels very intuitive to use.  &lt;/p&gt;

&lt;p&gt;Notice that in both cases the "redo" button gets disabled when the user edits "Hello" to "Hello Friend" (which makes sense if you remember that redo is undo of undo). The difference is in how undo behaves after that point (and as a result, any subsequent "redo"). &lt;/p&gt;

&lt;p&gt;I implemented both modes in my proof of concept. "History undo" mode is enabled by default.&lt;br&gt;
If you want to get into the nitty-gritty have a look at the &lt;a href="https://github.com/isaacHagoel/todo-replicache-sveltekit/blob/undo-redo/src/lib/undo/UndoManager.ts#L117" rel="noopener noreferrer"&gt;source code&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  The question of scope
&lt;/h3&gt;

&lt;p&gt;An important property of a good undo-redo implementation is that it operates in the correct scope. In most applications, the expectation is for the undo-manager's scope to be the entire session. As I continuously hit "undo," I expect all the actions I took in the session to be rolled back. If I close the session, I expect to my undo stack to be lost. &lt;br&gt;
If the scope is smaller than the whole session, it can be disorienting for users. For example, if your text-editor has a separate undo stack from the rest of the app, people will &lt;a href="https://x.com/astralwave/status/1805639315730612560" rel="noopener noreferrer"&gt;call you out on Twitter&lt;/a&gt;:&lt;/p&gt;

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

&lt;p&gt;In web applications or desktop applications that supports multiple tabs (e.g., &lt;a href="https://code.visualstudio.com/" rel="noopener noreferrer"&gt;VSCode&lt;/a&gt;), a session equals a single tab.&lt;/p&gt;

&lt;p&gt;The expectations I described above carry over from single-user to collaborative, multi-user applications. Users expect to only be able to undo/redo their own actions. Users don't seem to expect the undo/redo stack to exceed session scope e.g. be shared between multiple tabs or multiple devices (on which they have the app running). I wonder if it's just because no app has done that yet and once someone does, it would become the new norm. Wouldn't it be great to have your history available on all your devices?&lt;/p&gt;

&lt;p&gt;In my implementation I remained within session-scope, like all standard implementations. I did it because it was the easier and quicker option. I might try to extend it to user-scope in future iterations. I am sure it will present interesting technical challenges.&lt;/p&gt;
&lt;h3&gt;
  
  
  Memento vs. Command pattern
&lt;/h3&gt;

&lt;p&gt;One of the first resources I stumbled upon when I was doing my research was &lt;a href="https://redux.js.org/usage/implementing-undo-history" rel="noopener noreferrer"&gt;an article in the official Redux docs&lt;/a&gt;, explaining how to implement generic undo/redo using the &lt;a href="https://en.wikipedia.org/wiki/Memento_pattern" rel="noopener noreferrer"&gt;Memento pattern&lt;/a&gt;. The idea is so simple: Just save a copy of the state every time it changes and store these state copies as "past" (array), "present" and "future" (array). Whenever you want to undo, push the present state into the "future" array, pop the head of the "past" array and make it the new present, the app re-renders, voilà. No need for app-specific logic, whatever your state shape is - it just works. It sounds so alluring, so beautiful and elegant - there is only one tiny problem: it doesn't work for anything besides the simplest of apps.  &lt;/p&gt;

&lt;p&gt;Here is why:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It doesn't deal with side-effects. Undoing and redoing actions tends to involve much more than state changes on the frontend. Apps need to create or cleanup remote resources, call APIs and do all the stuff that real apps do. These side-effects and the logic for addressing them are specific, and need to be handled on a case by case basis by the app. In other words, transition between states involves more than just replacing one state object with another.&lt;/li&gt;
&lt;li&gt; The idea of replacing the state object wholesale is totally incompatible with simultaneous, collaborative editing. It leads to users constantly erasing each other's changes even if they are modifying different parts of the state.&lt;/li&gt;
&lt;li&gt;Even in React apps that use Redux, not all the state is managed by the central store. There is a bunch of local state in the components. Don't we want the undo/redo manager to be able to account for local component state as well?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Sadly, due to of all of the above, the Memento pattern is off the table. This leave us with the much less plug-and-play  &lt;a href="https://en.wikipedia.org/wiki/Command_pattern" rel="noopener noreferrer"&gt;Command pattern&lt;/a&gt;. Instead of storing states we store commands and reverse-commands and execute them whenever we need to roll the state back or forward. "Commands" is just a fancy name for functions that modify state, e.g. "() =&amp;gt; markTodoComplete(id, true)" and its reverse "() =&amp;gt; markTodoComplete(id, false)".&lt;/p&gt;

&lt;p&gt;The command pattern allows us to update the state granularly with less collisions.  It allows us to apply arbitrary logic and deal with side effects, and it doesn't know or care about the application state or where it lives. These advantages come at a cost: For every action our app can perform, we now need to implement a reverse-action, and register both with the undo manager. But wait, there's more...  &lt;/p&gt;
&lt;h3&gt;
  
  
  We can still have conflicts, can't we?
&lt;/h3&gt;

&lt;p&gt;Multiple users making changes to the same "document" at the same time means that conflicts can and will occur. Having undo-redo thrown into the mix increases the likelihood of conflicts by introducing the possibility of unintentional and hidden conflicts. When real-time editing, users usually try to stay out of each other's way but the dimension of time makes that more tricky. If I edited a place at an earlier time, and later someone made changes on top of my changes, what's gonna happen if I casually "undo" ten times? I can unintentionally cause someone else to lose work. This can also happen via indirect conflicts - for example: user A creates an item, user B edits the item, user A clicks undo - deleting the item and deleting user B's work as a result (again, without intending or even realising it).&lt;br&gt;
This sounds bad, right? The whole point of undo/redo is to allow users to experiment and time-travel safely, without worrying about accidentally corrupting the system's state. &lt;/p&gt;

&lt;p&gt;Sync engines, like Replicache (which our little todo app uses), have the ability to deal with "realtime" conflicts between clients via an authoritative server that can reject and revert changes. However, we don't want the user experience errors due to rejected undo operations, or have nothing happen because the element they are trying to modify no longer exists. See it happening in Figma in the gif below (taken from &lt;a href="https://github.com/rocicorp/undo/issues/12#issuecomment-2172386581" rel="noopener noreferrer"&gt;here&lt;/a&gt;). Notice how some undo operations do nothing and the user needs to keep undoing to drain all the bad operations from the undo-stack. That's poor UX. We need to come up with an elegant way to deal with these situations.&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%2F0g21xwrnq2k9muaafsy1.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0g21xwrnq2k9muaafsy1.gif" alt="figma_undo" width="644" height="418"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Can we simply ignore conflicts?
&lt;/h3&gt;

&lt;p&gt;Some smart people don't think that conflicts are a big deal in multiplayer systems. Adam Wiggins (co-founder of Heroku) for example, dismissed it in &lt;a href="https://youtu.be/WEFuEY3fHd0?si=EmhrAV8LUYkhSk2V&amp;amp;t=794" rel="noopener noreferrer"&gt;this part of his recent talk&lt;/a&gt; (not in the context of undo/redo but as a general concern). He was later challenged about it by a question from the audience at &lt;a href="https://youtu.be/WEFuEY3fHd0?si=iw5tVQcWm2VvHgAb&amp;amp;t=1756" rel="noopener noreferrer"&gt;this timestamp&lt;/a&gt; but stood his ground. To summarise his reasoning: Users stay out of each others' way - it's a social thing (true). Also, when conflicts do occur, users are smart enough to realise what happened and fix it themselves, no big deal. He does note that this is true for the app he's creating (&lt;a href="https://museapp.com/" rel="noopener noreferrer"&gt;Muse&lt;/a&gt;), but might not apply in all cases.&lt;/p&gt;

&lt;p&gt;I have to respectfully disagree. It's cool that users find creative ways to work with broken systems, but we can't use that as an excuse for building sub-par apps. We can and should do better for our users.&lt;/p&gt;
&lt;h3&gt;
  
  
  Dealing with conflicts - undo-manager perspective (in theory)
&lt;/h3&gt;

&lt;p&gt;So, how can we deal with these nasty conflicts? &lt;br&gt;
&lt;a href="https://web.eecs.umich.edu/~aprakash/papers/undo-tochi94.pdf" rel="noopener noreferrer"&gt;This paper&lt;/a&gt; lays down a solid foundation. I'll do my best to summarise its main ideas for you. The paper discusses the problem of "undoing actions in collaborative systems" in the context of a distributed text editor called DistEdit. It suggests that the undo-manager takes a "Conflict(A,B)" callback from the app, with the following spec (see section 4.2.1):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The Conflict(A, B) function supplied by the application must return true if the adjacent operations A and B performed in sequence cannot be reordered, and false otherwise . The importance of the notion of conflict is that it imposes an ordering on operations A and B. If Conflict(A, B) is true, then the order of operations A and B cannot, in general, be changed without affecting the results. Furthermore, in general, operation A cannot be undone, unless the following operation B is undone&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The paper then offers an insight about the users' intentions (see section 5 "Selective Undo Algorithms"): &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If an operation A is undone, we assume that users want their document to go to a state that it would have gone to if operation A had never been performed, but all other operations had been performed&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To achieve this, the proposed algorithm first rolls back everything that came after the operation we want to undo, makes a backup copy of the "future" stack, and tries to preform the undo by "bubbling" the operation we are trying to get rid off up the stack, kinda like &lt;a href="https://www.geeksforgeeks.org/bubble-sort-algorithm/" rel="noopener noreferrer"&gt;bubble sort&lt;/a&gt;. In each step it checks whether the operation we want to undo (A) has conflict with the next adjacent operation (B). If not, they can be swapped, and we can executed a "transposed" version of B without executing A. &lt;br&gt;
This "transpose(A,B)" function, that the app needs to provide, makes sense in the context of text editing, where the cursor position, for example, could be different if operation A never happened. The algorithm keeps working its way up the stack until it either reaches the top (success) or hits a conflict. If it hits a conflict, it tries to get rid of it by checking whether the "future" has the reverse operation; if yes, both can be safely removed. If the conflict cannot be removed, the algorithm determines that the operation cannot be undone (failure). When that happens, the paper offers two options (see section 8.1.4):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Show the conflicts to the user and ask them to resolve.&lt;/li&gt;
&lt;li&gt;Tell the user about the conflict, ignore that undo operation, and allow the user to undo older operations. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;While I like the general idea, I had some issues with this algorithm:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;For a general-purpose undo-manager, outside of collaborative text editing (which most apps use &lt;a href="https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type" rel="noopener noreferrer"&gt;CRDTs&lt;/a&gt; for nowadays), it seemed excessive to expect the app to provide transpose functions, which must satisfy five mathematical properties (see section 4.2.2 in the paper).&lt;/li&gt;
&lt;li&gt;I don't want users to be able try to undo something and end up failing. I want to detect the conflicts ahead of time - for better UX.&lt;/li&gt;
&lt;li&gt;While I think it makes sense to skip bad operations, I am not sure that it's a good idea to ask the user to do something about it. If we wanted that we should have implemented features like version-history or version-control rather than undo/redo. Keeping the user informed is, generally speaking, a good idea, but we should aspire to do it in a non-disruptive manner.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Dealing with conflicts - undo-manager perspective (in practice)
&lt;/h3&gt;

&lt;p&gt;With all this in mind, I ended up with the following implementation: &lt;br&gt;
The undo-manager allows each entry (action) to have the attributes 'scopeName', 'hasUndoConflict', and 'hasRedoConflict'. Unlike the "Conflict(A,B)" function from the paper, which takes two adjacent operations, my functions check whether a single undo or redo operation is valid in the context of the current state of the app. &lt;/p&gt;

&lt;p&gt;The conflict checks run on the "head" of the undo and redo stacks after every operation, and remove conflicting entries (and everything else with the same scopeName) until a non-conflicting one is found. This way, the next action the user can take is always a non-conflicting one.&lt;br&gt;
The undo-manager provides a way to tell the user when conflicting entries are removed (via a "change reason"), but in the demo app I used it in a very subtle way. &lt;/p&gt;

&lt;p&gt;All in all, here is the type definition for a single undo-redo entry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;UndoEntry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;reverseOperation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;hasUndoConflict&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;hasRedoConflict&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;boolean&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;// determines what gets removed when there are conflicts&lt;/span&gt;
    &lt;span class="nl"&gt;scopeName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// will be returned from subscribeToCanUndoRedoChange so you can display it in a tooltip next to the buttons (e.g "un-create item")&lt;/span&gt;
    &lt;span class="nl"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;reverseDescription&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the full implementation details and how it is used, have a look at the code, for example &lt;a href="https://github.com/isaacHagoel/todo-replicache-sveltekit/blob/a75ff89f62e1cb1e0196390586126bdff6cb733e/src/routes/list/%5BspaceID%5D/%2Bpage.svelte#L171" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dealing with conflicts - app perspective
&lt;/h3&gt;

&lt;p&gt;So the undo-manager facilitates a way to deal with conflicts, but it's up to the app to provide the actual conflict-checking logic. How should it go about that? One useful concept is "ownership".&lt;/p&gt;

&lt;p&gt;In a single-user app, there is no question about who owns the data - there is only one user, but what about a multi-user, collaborative app? We can think about it as follows: The last user who modified a piece of data (direct modification - not via undo or redo) owns it. The owner of a piece of data can safely undo/redo their changes to it without overriding someone else's changes.&lt;br&gt;
For example, if I created a todo item and you modified its description, I shouldn't be able to undo the creation of that item, but I should still be able to undo anything else that I have in my undo stack. The granularity of the ownership is determined by the app. If I modified the text of an item and you then marked it as completed, it is probably okay for me to undo my change because I own the description and you own the completeness. If I later directly modify the completeness, I should be able to undo and redo that, and you shouldn't, because I took ownership over completeness.&lt;/p&gt;

&lt;p&gt;The gif below shows a simple example: When the user on the right edits the text of the second item, the user on the left loses the ability to undo any edits to its text or its creation, but still has the ability to undo the creation of the first item (which they still own). A sharp-eyed viewer would notice that the undo icon on the left animates when the conflicting entries are removed (when the user on the right enters the text "nope!"). &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%2Fni22vrc72vkt2amxt20f.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fni22vrc72vkt2amxt20f.gif" alt="undo_demo_conflict" width="714" height="376"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you don't agree with the specific logic I applied here, that's okay - the logic is totally flexible. The important thing is that we have an undo-manager that makes this possible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting the user experience right
&lt;/h3&gt;

&lt;p&gt;In the gif above, did you notice that little tooltip (just a "title" attribute in this demo) that tells the user what's going to happen when they click undo/redo when they hover over the button? Did you notice how the buttons animate when there is a change in the undo/ redo stack? To achieve these, the undo-manager provides &lt;a href="https://github.com/isaacHagoel/todo-replicache-sveltekit/blob/undo-redo/src/lib/undo/simplePubSub.ts" rel="noopener noreferrer"&gt;a simple pub-sub service&lt;/a&gt; so that the consumer can stay up to speed.&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%2Fgithub.com%2FisaacHagoel%2Ftodo-replicache-sveltekit%2Fassets%2F20507787%2F6500dfb3-ad91-4d1e-b7d7-a9f69271b261" class="article-body-image-wrapper"&gt;&lt;img alt="next undo operation tooltip" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2FisaacHagoel%2Ftodo-replicache-sveltekit%2Fassets%2F20507787%2F6500dfb3-ad91-4d1e-b7d7-a9f69271b261" width="518" height="158"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Dealing with asynchrony
&lt;/h3&gt;

&lt;p&gt;The undo-manager has to be able to handle both synchronous and asynchronous operations because all the Replicache calls are async, and in other real-world systems, any call to the backend or external APIs would be async as well. The challenge with async operations is that they can complete in different order to how they start (depending on how long it takes each promise to resolve) and query the system while it's "between states." For example, an operation starts running, and then while it's awaiting something, an "undo" or a conflict check starts running and make their own changes. To deal with that, I introduced &lt;a href="https://github.com/isaacHagoel/todo-replicache-sveltekit/blob/undo-redo/src/lib/undo/serialAsyncExecutor.ts" rel="noopener noreferrer"&gt;a simple module&lt;/a&gt; that executes the async operations serially using a queue.&lt;/p&gt;

&lt;h3&gt;
  
  
  Places where my demo implementation falls short
&lt;/h3&gt;

&lt;p&gt;If you read my &lt;a href="https://dev.to/isaachagoel/are-sync-engines-the-future-of-web-applications-1bbi"&gt;previous post&lt;/a&gt;, you’d know that I was thoroughly impressed by Replicache. That's still true, but I have hit some walls (missing features) when adding undo-redo to the app. It’s important to note that the undo-manager itself is generic (agnostic about Replicache) but does pose some expectations that Replicache fails to meet (all seem fixable). Here are the main challenges I faced:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Distinguishing between self inflicted and external updates&lt;/strong&gt;: When Replicache informs the app about incoming changes from the server, it doesn’t indicate whether these changes originate in the current session or in some other session (external). We’d like to initiate conflict checks when there are incoming external changes, otherwise they already ran when taking the action (before sending it to the server), but how? I ended up adding some &lt;a href="https://github.com/isaacHagoel/todo-replicache-sveltekit/blob/undo-redo/src/routes/list/%5BspaceID%5D/%2Bpage.svelte#L26" rel="noopener noreferrer"&gt;app-specific logic&lt;/a&gt; to detect that. Ideally, once Replicache exposes that info (relevant issue &lt;a href="https://github.com/rocicorp/replicache/issues/1058" rel="noopener noreferrer"&gt;here&lt;/a&gt;), this kind of hack won’t be needed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Failure of an optimistic update&lt;/strong&gt;: Replicache uses optimistic updates, meaning that the server can reject operations or return with a different than expected outcome. When that happens, the state is rolled back and adjusted to reflect the server state. When Replicache does that, it doesn’t notify the app, it comes in as any other state update. That makes it hard to adjust the undo stack, which still contains the original updates that the server just rejected. I could have probably worked around it but opted to leave it unimplemented in this POC. Ideally, Replicache would expose that information in the future.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coming back from offline mode&lt;/strong&gt;: If you go offline, you can do anything you want (including undo/redo) and when you come back online your changes will be pushed to the server and override anything other users did while you were offline. This problem is not specific to undo/redo but a result of the &lt;a href="https://www.linkedin.com/pulse/last-write-wins-database-systems-yeshwanth-n-emc8c/" rel="noopener noreferrer"&gt;Last Write Wins&lt;/a&gt; conflict-resolution strategy that my app uses. In theory, this could be mitigated by adding more sophisticated logic to the server’s mutators, but that would require more research to get right (maybe a good subject for another post).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lack of Integration with CRDT-based undo&lt;/strong&gt;: Supporting embedded text editors in a way that is user friendly remains a challenge (I haven't attempted it yet, another idea for a future post :)).&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Closing thoughts
&lt;/h3&gt;

&lt;p&gt;We've covered a lot of ground in this post. While I spent most of it discussing different aspects of the undo-manager, in reality the majority of my time and effort were spent on carefully thinking through and implementing the app's reverse-operations and conflict-checking logic. &lt;/p&gt;

&lt;p&gt;In some cases, I had to refactor the app and break down operations to make them undo/redo-friendly. For example, “completeAllItems” couldn’t remain a simple loop that calls “updateItem” with each item-id; it had to become its own thing with its own reverse logic (because maybe another user added or edited items). Some changes to the backend were required as well, such as adding an “un-delete” operation, which is different from “create” because it preserves the original sort position of the item. The database schema changed because I needed to add an "updatedBy" field on each todo, and these are just some examples. Testing is another task that grows considerably when your app has undo-redo. &lt;/p&gt;

&lt;p&gt;In other words, undo-redo is one of those features that make every other feature in your app more complicated and time-consuming to implement and maintain. Is it worth it? I think the answer is a resounding yes for productivity apps and content-editors of any kind, but you need to know what you’re getting yourself into. It is definitely not for the faint of heart.&lt;/p&gt;

&lt;p&gt;Thank you for reading. Feel free to leave a comment if you have any questions or insights. &lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>programming</category>
      <category>learning</category>
    </item>
    <item>
      <title>Are Sync Engines The Future of Web Applications?</title>
      <dc:creator>Isaac Hagoel</dc:creator>
      <pubDate>Mon, 17 Jun 2024 21:13:15 +0000</pubDate>
      <link>https://dev.to/isaachagoel/are-sync-engines-the-future-of-web-applications-1bbi</link>
      <guid>https://dev.to/isaachagoel/are-sync-engines-the-future-of-web-applications-1bbi</guid>
      <description>&lt;p&gt;Look at the GIF below — it shows a real-time &lt;a href="https://todo-replicache-sveltekit.onrender.com/" rel="noopener noreferrer"&gt;Todo-MVC demo&lt;/a&gt;, syncing across windows and smoothly transitioning in and out of offline mode. While it’s just a simple demo app, it showcases important, cutting-edge concepts that every web developer should know. This is a &lt;a href="https://replicache.dev/" rel="noopener noreferrer"&gt;Replicache&lt;/a&gt; demo app that I ported from an Express backend and web components frontend to SvelteKit to learn about the technology and concepts behind it. I want to share my learnings with you. The source code is available &lt;a href="https://github.com/isaacHagoel/todo-replicache-sveltekit" rel="noopener noreferrer"&gt;on Github&lt;/a&gt;.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Context and motivation
&lt;/h2&gt;

&lt;p&gt;Web applications face some fundamentally hard problems, problems most   web frameworks seem to ignore. These problems are so hard that only very few apps actually solve them well, and those apps stand head and shoulders above other apps in their respective space.&lt;br&gt;&lt;br&gt;
Here are some such problems I had to deal with in actual commercial apps I worked on: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Getting the app to feel snappy even when it talks to the server, even over slow or patchy network. This applies not only to the initial load time but also to interactions after the app has loaded. &lt;a href="https://developer.mozilla.org/en-US/docs/Glossary/SPA" rel="noopener noreferrer"&gt;SPAs&lt;/a&gt;were an early and ultimately insufficient attempt at solving this.&lt;/li&gt;
&lt;li&gt;Implementing undo/ redo and version history for user generated content (e.g site building, e-commerce, online courses builder).&lt;/li&gt;
&lt;li&gt;Getting the app to work correctly when open simultaneously by the same user on multiple tabs/ devices. &lt;/li&gt;
&lt;li&gt;Handling long-lived sessions running an old version of the frontend, which users might not want to refresh to avoid losing work.&lt;/li&gt;
&lt;li&gt;Making collaboration features/multiplayer functionalities work correctly and near real-time, including conflict resolution.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I encountered these problems while working on totally normal web applications, nothing too crazy, and I believe most web apps will hit some or all of them as they gain traction. &lt;br&gt;
A pattern I noticed in dev teams that start working on a new product is to ignore these problems completely, even if the team is aware of them. The reasoning is usually along the lines of "we'll deal with it when we start actually having these problems." The team would then go on to pick some well-established frameworks (pick your favorite) thinking these tools surely offer solutions to any common problem that may arise. Months later, when the app hits ten thousand active users, reality sinks in: the team has to introduce partial, patchy solutions that add complexity and make the system even more sluggish and buggy, or rewrite core parts (which no one ever does right after launch). Ouch. &lt;br&gt;
I felt this pain. The pain is real. &lt;br&gt;
Enter "Sync Engine."&lt;/p&gt;

&lt;h2&gt;
  
  
  What the hell is a sync engine?
&lt;/h2&gt;

&lt;p&gt;Remember I said that some apps address these issues much better than others? Recent famous examples are &lt;a href="https://linear.app/isaach" rel="noopener noreferrer"&gt;Linear&lt;/a&gt; and &lt;a href="https://www.figma.com/" rel="noopener noreferrer"&gt;Figma&lt;/a&gt;. Both have disrupted incredibly competitive markets by being technologically superior. Other examples are &lt;a href="https://superhuman.com/" rel="noopener noreferrer"&gt;Superhuman&lt;/a&gt; and a decade prior, &lt;a href="https://trello.com/" rel="noopener noreferrer"&gt;Trello&lt;/a&gt;. When you look into what they did, you discover that they all converged on very similar patterns, and they all developed their respective implementations in-house. You can read about how they did it (highly recommended) in these links: &lt;a href="https://www.figma.com/blog/how-figmas-multiplayer-technology-works/" rel="noopener noreferrer"&gt;Figma&lt;/a&gt;, &lt;a href="https://www.youtube.com/live/WxK11RsLqp4?feature=share&amp;amp;t=2175" rel="noopener noreferrer"&gt;Linear&lt;/a&gt;, &lt;a href="https://blog.superhuman.com/superhuman-is-built-for-speed/" rel="noopener noreferrer"&gt;Superhuman&lt;/a&gt;, &lt;a href="https://www.atlassian.com/engineering/sync-architecture" rel="noopener noreferrer"&gt;Trello (series)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;At the core of the system, there is always a sync engine that acts as a persistent buffer between the frontend and the backend. At a high level, this is how it works:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The client always reads from and writes to a local store that is provided by the engine. As far as the app code is concerned, it runs locally in memory.&lt;/li&gt;
&lt;li&gt;That store is responsible for updating the state optimistically, persisting the data locally in the browser's storage, and syncing it back and forth with the backend, including dealing with potential complications and edge cases.&lt;/li&gt;
&lt;li&gt;The backend implements the other half of the engine, to allow pulling and pushing data, notifying the clients when data has changed, persisting the data in a database, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Different implementations of sync engines make different tradeoffs, but the basic idea is always the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  Not a new idea but...
&lt;/h2&gt;

&lt;p&gt;If you've been following trends in the web-dev world, you'd know that sync engines have been a centrepiece in several of them, namely: &lt;a href="https://web.dev/articles/what-are-pwas" rel="noopener noreferrer"&gt;progressive web apps&lt;/a&gt;, &lt;a href="https://offlinefirst.org/" rel="noopener noreferrer"&gt;offline-first apps&lt;/a&gt;, and the lately trending term: &lt;a href="https://www.inkandswitch.com/local-first/" rel="noopener noreferrer"&gt;local-first software&lt;/a&gt;. You might have even looked into some of the databases that offer a built-in sync engine such as &lt;a href="https://pouchdb.com/" rel="noopener noreferrer"&gt;PouchDb&lt;/a&gt; or online services that do the same (e.g., &lt;a href="https://firebase.google.com/docs/firestore" rel="noopener noreferrer"&gt;Firestore&lt;/a&gt;). I have too, but my general feeling over the last few years has been that none of it is quite hitting the nail on the head. Progressive web apps were about users "installing" shortcuts to websites on their home screens as if they were native apps, despite not needing installation being maybe "the" benefit of the web. "Offline-first" made it sound like offline mode is more important than online, which for 99% of web apps is simply not the case. "Local-first" is admittedly the best name so far, but the official &lt;a href="https://www.inkandswitch.com/local-first/" rel="noopener noreferrer"&gt;local-first manifesto&lt;/a&gt; talks about peer-to-peer communication and &lt;a href="https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type" rel="noopener noreferrer"&gt;CRDTs&lt;/a&gt; (a super cool idea but one that is rarely used for anything besides collaborative text editing) in a world of full client-server web applications that are trying to solve practical problems like the ones I described above. Ironically, many tools that are part of the current "local-first" wave adopted the name without adopting all the principles.&lt;/p&gt;

&lt;p&gt;The one that drew my attention and interest the most is called "Replicache." Specifically, I was intrigued by it exactly because it's NOT a self-replicating database or a black-box SaaS service that you have to build your entire app around. Instead, it offers much more control, flexibility, and separation of concerns than any off-the-shelf solution I have encountered in this space.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Replicache?
&lt;/h2&gt;

&lt;p&gt;Replicache is a library. On the frontend, it requires very little wiring and effectively functions as a normal global store (think Zustand or a Svelte store). It has a chunk of state (in our example, each list has its own store). It can be mutated using a set of user-defined functions called "mutators" (think reducers) like "addItem", "deleteItem," or anything you want, and exposes a subscribe function (I am simplifying, full API &lt;a href="https://doc.replicache.dev/api/classes/Replicache" rel="noopener noreferrer"&gt;here&lt;/a&gt;). &lt;/p&gt;

&lt;p&gt;Behind this familiar interface lies a robust and performant client-side sync engine that handles:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Initial full download of the relevant data to the client.&lt;/li&gt;
&lt;li&gt;Pulling and pushing "mutations" to and from the backend. A mutation is an event that specifies which mutator was applied, with which parameters (plus some metadata).

&lt;ul&gt;
&lt;li&gt;When pushing, these changes are applied optimistically on the client, and rolled back if they fail on the server. Any other pending changes would be applied on top (rebase).&lt;/li&gt;
&lt;li&gt;The sync mechanism also includes queuing changes if the connection is lost, retry mechanisms, applying changes in the right order, and de-duping.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Caching everything in memory (performance) and persisting it to the browser storage (specifically IndexedDB) for backup.&lt;/li&gt;
&lt;li&gt;Since the same storage is accessible from all the tabs of the same application, the engine deals with all the implications of that—like what to do when there was a schema change but some tabs have refreshed and some haven't and are still using the old schema.&lt;/li&gt;
&lt;li&gt;Keeping all the tabs in sync instantly using a broadcast channel (since relying on the shared storage is not fast enough).&lt;/li&gt;
&lt;li&gt;Dealing with cases in which the browser decides to wipe out the local storage.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You might have noticed that this right here addresses a big chunk of the problems I listed at the top of this post. Being mutations-based also lends itself to features like undo/redo.&lt;/p&gt;

&lt;p&gt;In order for all of this to work, it's your backend's job to implement the protocol that Replicache defines. Specifically:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You need to implement &lt;a href="https://doc.replicache.dev/reference/server-push" rel="noopener noreferrer"&gt;push&lt;/a&gt; and &lt;a href="https://doc.replicache.dev/reference/server-pull" rel="noopener noreferrer"&gt;pull&lt;/a&gt; APIs. These endpoints need to be able to activate mutators similarly to the frontend (though they don't have to run the same logic). The backend is authoritative, and conflict resolution is done by your code within the mutator implementation.&lt;/li&gt;
&lt;li&gt;Your database needs to support snapshot isolation and run operations within transactions.&lt;/li&gt;
&lt;li&gt;The Replicache client polls the server periodically to check for changes, but if you want close to real-time sync between clients, you need to implement a "poke" mechanism, namely a way to notify the clients that something has changed and they need to pull now. This could be done via &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events" rel="noopener noreferrer"&gt;server-sent events&lt;/a&gt; or &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSocket" rel="noopener noreferrer"&gt;websockets&lt;/a&gt;. It's an interesting API design choice—changes are never pushed to the client; the client always pulls them. I believe it is done this way for simplicity and ease of reasoning about the system. One thing for sure: it's good that they didn't make websockets mandatory because that would have made the protocol incompatible with HTTP (server-sent events stream over a normal HTTP connection), which would have required extra infrastructure and presented additional integration challenges.&lt;/li&gt;
&lt;li&gt;Depending on the &lt;a href="https://doc.replicache.dev/strategies/overview" rel="noopener noreferrer"&gt;versioning strategy&lt;/a&gt;, you might need to implement additional operations (e.g., createSpace).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If it sounds non-trivial to you, you are right. I don't think I fully wrapped my head around all the details of how it operates with the database. I'll need to do a follow-up project in which I totally refactor the database structure and/or add meaningful features to the example (e.g., version history) in order to get closer to fully grokking it. The thing is, I know how valuable this level of control is when building and maintaining real production apps. In my book, spending a week or two thinking deeply about and setting up the core part of your application is a great investment if it creates a strong foundation to build and expand upon.&lt;/p&gt;

&lt;h2&gt;
  
  
  Porting a non-trivial example
&lt;/h2&gt;

&lt;p&gt;The best (and arguably only) way to learn anything new is by getting your hands dirty—dirty enough to experience some of the tradeoffs and implications that would affect a real app. As I was going over the &lt;a href="https://doc.replicache.dev/examples/todo" rel="noopener noreferrer"&gt;examples on the Replicache website&lt;/a&gt;, I noticed there were none for Sveltekit. I am a huge Svelte fan since Svelte 3 was released, but only recently started playing with Sveltekit. I thought this would be an awesome opportunity to learn by doing and create a useful reference implementation at the same time.&lt;/p&gt;

&lt;p&gt;Porting an existing codebase to a different technology is educational because, as you translate the code, you are forced to understand and question it. Throughout the process, I experienced multiple eureka moments as things that seemed odd at first clicked into place.&lt;/p&gt;

&lt;h2&gt;
  
  
  Learnings
&lt;/h2&gt;

&lt;h4&gt;
  
  
  Sveltekit
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;Sveltekit &lt;a href="https://github.com/sveltejs/kit/issues/1491" rel="noopener noreferrer"&gt;doesn't natively support WebSockets&lt;/a&gt;, and even though it does support server-sent events, it does so in a &lt;a href="https://stackoverflow.com/questions/74879852/how-can-i-implement-server-sent-events-sse-in-sveltekit" rel="noopener noreferrer"&gt;clumsy way&lt;/a&gt;. Express supports both nicely. As a result, I used &lt;a href="https://github.com/razshare/sveltekit-sse" rel="noopener noreferrer"&gt;svelte-sse&lt;/a&gt; for server-sent events. One somewhat annoying quirk I ran into is that since svelte-sse returns a Svelte store, which my app wasn't subscribing to (the app doesn't need to read the value, just to trigger a pull as I described above), the whole thing was just optimized away by the compiler. I was initially scratching my head about why messages were not coming through. I ended up having to implement a workaround for that behavior. I don't blame the author of the library; they assumed a meaningful value would be sent to the client, which is not the case with 'poke'.&lt;/li&gt;
&lt;li&gt;SvelteKit's filesystem-based routing, load functions, layouts, and other features allowed for a better-organized codebase and less boilerplate code compared to the original Express backend. Needless to say, on the frontend, Svelte is miles ahead of web components, resulting in a frontend codebase that is smaller and more readable even though it has more functionality (the original example TodoMVC was missing features such as "mark all as complete" and "delete completed").&lt;/li&gt;
&lt;li&gt;Overall, I love Sveltekit and plan to keep using it in the future. If you haven't tried it, &lt;a href="https://learn.svelte.dev/tutorial/introducing-sveltekit" rel="noopener noreferrer"&gt;the official tutorial&lt;/a&gt; is an awesome introduction.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Replicache
&lt;/h3&gt;

&lt;p&gt;Overall, I am super impressed by Replicache and would recommend trying it out. At the basic level (which is all I got to try at this point), it works very well and delivers on all its promises. With that said, here are some general concerns (not todo app related) I have and thoughts related to them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Performance-related:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Initial load time&lt;/strong&gt; (first time, before any data was ever pulled to the client) might be long when there is a lot of data to download (think tens of MBs). Productivity apps in which the user spends a lot of time after the initial load are less sensitive to this, but it is still something to watch for. Potential mitigation: partial sync (e.g., Linear only sends open issues or ones that were closed over the last week instead of sending all issues).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chatty network (?)&lt;/strong&gt; - Initially, it seemed to me that there was a lot of chatter going back and forth between the client and the server with all the push, pull, and poke calls flying around. On deeper inspection, I realized my intuition was wrong. There is frequent communication, yes, but since the mutations are very compact and the poke calls are tiny (no payload), it amounts to much less than your normal REST/GraphQL app. Also, a browser full reload (refresh button or opening the page again in a new tab/window after it was closed) loads most of the data from the browser's storage and only needs to pull the diffs from the server, which leads me to the next point.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coming back after a long period of time offline&lt;/strong&gt;: I haven't tested this one, but it seems like a real concern. What happens if I was working offline for a few days making updates while my team was online and also making changes? When I come back online, I could have a huge amount of diffs to push and pull. Additionally, conflict resolution could become super difficult to get right. This is a problem for every collaborative app that has an offline mode and is not unique to Replicache. The Replicache docs &lt;a href="https://doc.replicache.dev/concepts/offline" rel="noopener noreferrer"&gt;warn about this situation&lt;/a&gt; and propose implementing "the concept of history" as a potential mitigation.&lt;/li&gt;
&lt;li&gt;What about &lt;strong&gt;bundle size&lt;/strong&gt;? Replicache is &lt;a href="https://bundlephobia.com/package/replicache@14.2.2" rel="noopener noreferrer"&gt;34kb gzipped&lt;/a&gt;, and for what you get in return, it's easily worth it.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://doc.replicache.dev/concepts/performance" rel="noopener noreferrer"&gt;This page&lt;/a&gt; on the Replicache website makes me think that, in the general case, performance should be very good.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Functionality-related:&lt;/strong&gt; 

&lt;ul&gt;
&lt;li&gt;Unlike native mobile or desktop apps, it is possible for users to &lt;strong&gt;lose the local copy of their work&lt;/strong&gt; because the browser's storage doesn't provide the same guarantees as the device's file system. Browsers can just decide to delete all the app's data under certain conditions. If the user has been online and has work that didn't have a chance to get pushed to the server, that work would be lost in such a case. Again, this problem is not unique to Replicache and affects all web apps that support offline mode, and based on what I read, it is unlikely to affect most users. It's just something to keep in mind.&lt;/li&gt;
&lt;li&gt;I was surprised to see that the &lt;strong&gt;schema in the backend database&lt;/strong&gt; in the Todo example I ported doesn't have the "proper" relational definitions I would expect from a SQL database. There is no "items" table with fields for "id", "text", or "completed". The reason I would want that to exist is the same reason I want a relational database in the first place—to be able to easily slice and dice the data in my system (which I always missed down the line when I didn't have). I don't think it is a major concern since Replicache is supposed to be backend-agnostic as long as the protocol is implemented according to spec. I might try to refactor the database as a follow-up exercise to see what that means in terms of complexity and ergonomics.&lt;/li&gt;
&lt;li&gt;I find &lt;strong&gt;version history and undo/redo&lt;/strong&gt; super useful and desirable in apps with user-editable content. With regards to undo/redo there is an &lt;a href="https://github.com/rocicorp/undo" rel="noopener noreferrer"&gt;official package&lt;/a&gt; but it seems to &lt;a href="https://github.com/rocicorp/replicache/issues/1008" rel="noopener noreferrer"&gt;lack support for the multiplayer usecase&lt;/a&gt; (which is where the problems come from). As for version-history, the Replicache documentation mentions "the concept of history" but &lt;a href="https://doc.replicache.dev/concepts/offline" rel="noopener noreferrer"&gt;suggests talking to them&lt;/a&gt; if the need arises. That makes me think it might not be straightforward to achieve. Another idea for a follow-up task.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Collaborative text editing&lt;/strong&gt; - the existing conflict resolution approach won't work well for collaborative text editing, which requires &lt;a href="https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type" rel="noopener noreferrer"&gt;CRDTs&lt;/a&gt; or &lt;a href="https://en.wikipedia.org/wiki/Operational_transformation" rel="noopener noreferrer"&gt;OT&lt;/a&gt;. I wonder how easy it would be to integrate Replicache with something like &lt;a href="https://yjs.dev/" rel="noopener noreferrer"&gt;Yjs&lt;/a&gt;. There is an &lt;a href="https://github.com/rocicorp/replicache-yjs" rel="noopener noreferrer"&gt;official example repo&lt;/a&gt;, but I haven't looked into it yet.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Scaling-related:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Since the server is stateful (holds open HTTP connections for server-sent events), I wonder how well it would scale. I've worked on production systems with &amp;gt;100k users that used WebSockets before, so I know it is not that big of a deal, but still something to think about.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Other:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;- In theory, Replicache can be &lt;strong&gt;added into existing apps&lt;/strong&gt; without rewriting the frontend (as long as the app already uses a similar store). The backend might be trickier. If your database doesn't support snapshot isolation, you are out of luck, and even if it does, the existing schema and your existing endpoints might need some serious rework. If you're going to use it, do it from day one (if you can).&lt;/li&gt;
&lt;li&gt;Replicache is &lt;strong&gt;not open source&lt;/strong&gt; (yet! see the point below) and is &lt;a href="https://replicache.dev/#pricing" rel="noopener noreferrer"&gt;free only as long as you're small or non-commercial&lt;/a&gt;. Given the amount of work (&amp;gt;2 years) that went into developing it and the quality of engineering on display, it seems fair. With that said, it makes adopting Replicache more of a commitment compared to picking up a free, open library. If you are a tier 2 and up paying customer, you get a &lt;a href="https://doc.replicache.dev/howto/source-access" rel="noopener noreferrer"&gt;source license&lt;/a&gt; so that if Replicache shuts down for some reason, your app is safe. Another option is to roll out your own sync engine, like the big boys (Linear, Figma) have done, but getting to the quality and performance that Replicache offers would be anything but easy or quick.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Crazy plot twist&lt;/strong&gt; (last minute edit): As I was about to publish this post I discovered that Replicache is going to be opened sourced in the near future and that its parent company is planning to launch a new sync-engine called "Zero". &lt;a href="https://zerosync.dev/" rel="noopener noreferrer"&gt;Here is the official announcement&lt;/a&gt;. It reads: "We will be open sourcing &lt;a href="https://replicache.dev/" rel="noopener noreferrer"&gt;Replicache&lt;/a&gt; and &lt;a href="https://reflect.net/" rel="noopener noreferrer"&gt;Reflect&lt;/a&gt;. Once Zero is ready, we will encourage users to move."
Ironically, Zero seems to be yet another solution that automagically syncs the backend database with the frontend database, which at least for me personally seems less attractive (because I want separation of concerns and control). With that said, these guys are experts in this domain and I am just a dude on the internet so we'll have to wait and see. In the meanwhile, I plan on playing with Replicache some more.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Should a sync engine be used for everything?
&lt;/h2&gt;

&lt;p&gt;No, a sync engine shouldn't be used for everything. The good news is that you can have parts of your app using it while other parts still submit forms and wait for the server's response in the conventional manner. SvelteKit and other full-stack frameworks make this integration easy.&lt;br&gt;
Obvious situations where using a sync engine is a bad idea:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Optimistic updates make sense only when client changes are highly likely to succeed (with rollbacks being rare) and when the client possesses enough information to predict outcomes. For instance, in an online test where a student's answer must be sent to the server for grading, optimistic updates (and hence a sync engine) wouldn't be feasible. The same applies to critical actions such as placing orders or trading stocks. A good rule of thumb is that any action dependent on the server and incapable of functioning offline should not rely on a sync engine.&lt;/li&gt;
&lt;li&gt;Any app dealing with huge datasets that cannot be fit on user machines. For example, creating a local-first version of Google or an analytics tool processing gigabytes of data to generate results is impractical. However, in scenarios where partial synchronisation suffices, a sync engine can still be beneficial. For instance, Google Maps can download and cache maps on client devices to operate offline, without needing high-resolution maps for every location worldwide all the time.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  A word on developer productivity and DX
&lt;/h2&gt;

&lt;p&gt;My impression is that having a sync engine can make DX (developer experience) much nicer. Frontend engineers just work with a normal store that they can subscribe to updates, and the UI always stays up to date. No need to think about fetching anything, calling APIs or server actions for the parts of the app that are governed by the sync engine. On the backend, I can't say much yet. It seems like it won't be harder than a traditional backend but I can't say for sure.&lt;/p&gt;

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

&lt;p&gt;It's exciting to imagine the future of web apps as planet scale, realtime multi-player collaboration tool that work reliably regardless of network conditions, while at the same time making these nasty problems I started this post with a thing of the past. &lt;br&gt;
I highly recommend fellow web developers to get themselves familiar with these new concepts, experiment with them and maybe even contribute. &lt;br&gt;
Thanks for reading. Leave a comment if you have any questions or thoughts. Peace.&lt;br&gt;
. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.S&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://youtu.be/cgTIsTWoNkM?si=Sssrbj09Z936QxEf" rel="noopener noreferrer"&gt;This interview&lt;/a&gt; with Aaron Boodman, the founder of the company that created Replicache, is great. Watch it and thanks me later. &lt;/p&gt;

</description>
      <category>webdev</category>
      <category>svelte</category>
      <category>javascript</category>
      <category>programming</category>
    </item>
    <item>
      <title>Svelte Reactivity Gotchas + Solutions (If you're using Svelte in production you should read this)</title>
      <dc:creator>Isaac Hagoel</dc:creator>
      <pubDate>Tue, 05 Oct 2021 02:26:13 +0000</pubDate>
      <link>https://dev.to/isaachagoel/svelte-reactivity-gotchas-solutions-if-you-re-using-svelte-in-production-you-should-read-this-3oj3</link>
      <guid>https://dev.to/isaachagoel/svelte-reactivity-gotchas-solutions-if-you-re-using-svelte-in-production-you-should-read-this-3oj3</guid>
      <description>&lt;p&gt;&lt;a href="https://svelte.dev/" rel="noopener noreferrer"&gt;Svelte&lt;/a&gt; is a great framework and my team has been using it to build production apps for more than a year now with great success, productivity and enjoyment. One of its core features is reactivity as a first class citizen, which is dead-simple to use and allows for some of the most expressive, declarative code imaginable: When some condition is met or something relevant has changed no matter why or how, some piece of code runs. It is freaking awesome and beautiful. Compiler magic.&lt;/p&gt;

&lt;p&gt;When you're just playing around with it, it seems to work in a frictionless manner, but as your apps become more complex and demanding you might encounter all sorts of puzzling, undocumented behaviours that are very hard to debug. &lt;br&gt;
Hopefully this short post will help alleviate some of the confusion and get back on track. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before we start, two disclaimers:&lt;/strong&gt; &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;All of the examples below are contrived. Please don't bother with comments like "you could have implemented the example in some other way to avoid the issue". I know. I promise to you that we've hit every single one of these issues in real codebases, and that when a Svelte codebase is quite big and complex, these situations and misunderstandings can and do arise.&lt;/li&gt;
&lt;li&gt;I don't take credit for any of the insights presented below. They are a result of working through the issues with my team members as well as some members of the Svelte community.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Gotcha #1: Implicit dependencies are evil
&lt;/h3&gt;

&lt;p&gt;This is a classic one. Let's say you write the following &lt;a href="https://svelte.dev/repl/c08fe37ebe054a6f9afd70f9b8535d45?version=3.43.1" rel="noopener noreferrer"&gt;code&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&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;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;9&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;sum&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sendSumToServer&lt;/span&gt;&lt;span class="p"&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="s2"&gt;sending&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nl"&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;sum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;a&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="nf"&gt;sendSumToServer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;a: &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"number"&lt;/span&gt; &lt;span class="na"&gt;bind:value=&lt;/span&gt;&lt;span class="s"&gt;{a}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/label&amp;gt;&lt;/span&gt; 
&lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;b: &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"number"&lt;/span&gt; &lt;span class="na"&gt;bind:value=&lt;/span&gt;&lt;span class="s"&gt;{b}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/label&amp;gt;&lt;/span&gt; 
&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;{sum}&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It all works (click to the REPL link above or &lt;a href="https://svelte.dev/repl/c08fe37ebe054a6f9afd70f9b8535d45?version=3.43.1" rel="noopener noreferrer"&gt;here&lt;/a&gt;) but then in code review you are told to extract a function to calculate the sum for "readability" or whatever other reason. &lt;br&gt;
You &lt;a href="https://svelte.dev/repl/05441a29b61f4dc2ab256af8cfc90ac0?version=3.43.1" rel="noopener noreferrer"&gt;do it&lt;/a&gt; and get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&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;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;9&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;sum&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;calcSum&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;a&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="p"&gt;}&lt;/span&gt;
    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sendSumToServer&lt;/span&gt;&lt;span class="p"&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="s2"&gt;sending&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nl"&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;calcSum&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nf"&gt;sendSumToServer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;a: &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"number"&lt;/span&gt; &lt;span class="na"&gt;bind:value=&lt;/span&gt;&lt;span class="s"&gt;{a}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/label&amp;gt;&lt;/span&gt; 
&lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;b: &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"number"&lt;/span&gt; &lt;span class="na"&gt;bind:value=&lt;/span&gt;&lt;span class="s"&gt;{b}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/label&amp;gt;&lt;/span&gt; 
&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;{sum}&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;The reviewer is happy but oh no, the code doesn't work anymore. Updating &lt;code&gt;a&lt;/code&gt; or &lt;code&gt;b&lt;/code&gt; doesn't update the sum and doesn't report to the server. Why?&lt;br&gt;
Well, the reactive block fails to realise that &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt; are dependencies. Can you blame it? Not really I guess, but that doesn't help you when you have a big reactive block with multiple implicit, potentially subtle dependencies and you happened to refactor one of them out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And it can get much worse...&lt;/strong&gt;&lt;br&gt;
Once the automatic dependency recognition mechanism misses a dependency, it loses its ability to run the reactive blocks in the expected order (a.k.a dependencies graph). Instead it runs them from top to bottom. &lt;br&gt;&lt;br&gt;
&lt;a href="https://svelte.dev/repl/2e16203056f34f019672ae9991a3cd32?version=3" rel="noopener noreferrer"&gt;This code&lt;/a&gt; yields the expected output because Svelte keeps track of the dependencies but &lt;a href="https://svelte.dev/repl/81eb5c6ed3924a10bcfb39aef87780b0?version=3" rel="noopener noreferrer"&gt;this version&lt;/a&gt; doesn't because there are hidden dependencies like we saw before and the reactive blocks ran in order. The thing is that if you happened to have the same "bad code" but in a different order &lt;a href="https://svelte.dev/repl/cc870c006a9a4c4a801e3278c3f72072?version=3.43.1" rel="noopener noreferrer"&gt;like this&lt;/a&gt;, it would still yield the correct result, like a landmine waiting to be stepped on.&lt;br&gt;
The implications of this are massive. You could have "bad code" that happens to work because all of the reactive blocks are in the "right" order by pure chance, but if you copy-paste a block to a different location in the file (while refactoring for example), suddenly everything breaks on you and you have no idea why. &lt;/p&gt;

&lt;p&gt;It is worth restating that the issues might look obvious in these examples, but if a reactive block has a bunch of implicit dependencies and it loses track of just one on of them, it will be way less obvious. &lt;br&gt;&lt;br&gt;
In fact, &lt;strong&gt;when a reactive block has implicit dependencies the only way to understand what the dependencies actually are is to read it very carefully in its entirety&lt;/strong&gt; (even if it is long and branching).&lt;br&gt;
This makes implicit dependencies evil in a production setting. &lt;/p&gt;
&lt;h4&gt;
  
  
  Solution A - functions with explicit arguments list:
&lt;/h4&gt;

&lt;p&gt;When calling functions from reactive blocks or when refactoring, only use functions that take all of their dependencies explicitly as arguments, so that the reactive block "sees" the parameters being passed in and "understands" that the block needs to rerun when they change - like &lt;a href="https://svelte.dev/repl/9f4a2172a5f74f6ba5f4dd9d73c43111?version=3.43.1" rel="noopener noreferrer"&gt;this&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&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;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;9&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;sum&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;calcSum&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="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;a&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="p"&gt;}&lt;/span&gt;
    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sendSumToServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&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="s2"&gt;sending&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nl"&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;calcSum&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="nf"&gt;sendSumToServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;a: &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"number"&lt;/span&gt; &lt;span class="na"&gt;bind:value=&lt;/span&gt;&lt;span class="s"&gt;{a}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/label&amp;gt;&lt;/span&gt; 
&lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;b: &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"number"&lt;/span&gt; &lt;span class="na"&gt;bind:value=&lt;/span&gt;&lt;span class="s"&gt;{b}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/label&amp;gt;&lt;/span&gt; 
&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;{sum}&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I can almost hear some of you readers who are functional programmers saying "duh", still I would go for solution B (below) in most cases because even if your functions are more pure you'll need to read the entire reactive block to understand what the dependencies are.&lt;/p&gt;

&lt;h4&gt;
  
  
  Solution B - be explicit:
&lt;/h4&gt;

&lt;p&gt;Make all of your dependencies explicit at the top of the block. I usually use an &lt;code&gt;if&lt;/code&gt; statement with all of the dependencies at the top. Like &lt;a href="https://svelte.dev/repl/4631502956b948659987f8e171927dc7?version=3.43.1" rel="noopener noreferrer"&gt;this&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&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;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;9&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;sum&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;calcSum&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;a&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="p"&gt;}&lt;/span&gt;
    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sendSumToServer&lt;/span&gt;&lt;span class="p"&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="s2"&gt;sending&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nl"&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="nf"&gt;isNaN&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="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;isNaN&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="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;calcSum&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nf"&gt;sendSumToServer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;a: &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"number"&lt;/span&gt; &lt;span class="na"&gt;bind:value=&lt;/span&gt;&lt;span class="s"&gt;{a}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/label&amp;gt;&lt;/span&gt; 
&lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;b: &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"number"&lt;/span&gt; &lt;span class="na"&gt;bind:value=&lt;/span&gt;&lt;span class="s"&gt;{b}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/label&amp;gt;&lt;/span&gt; 
&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;{sum}&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I am not trying to say that you should write code like this when calculating the sum of two numbers. The point I am trying to make is that in the general case, such a condition at the top makes the block more readable and also immune to refactoring. It does require some discipline (to not omit any of the dependencies) but from experience it is not hard to get right when writing or changing the code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gotcha #2: Primitive vs. object based triggers don't behave the same
&lt;/h3&gt;

&lt;p&gt;This is not unique to Svelte but Svelte makes it less obvious imho. &lt;br&gt;
Consider &lt;a href="https://svelte.dev/repl/942519e4be0c4679b2600a99808e163a?version=3" rel="noopener noreferrer"&gt;this&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;isForRealz&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&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;isForRealzObj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;makeTrue&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;isForRealz&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;isForRealzObj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&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="nl"&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;isForRealz&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="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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;isForRealz became true&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nl"&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;isForRealzObj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&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="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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;isForRealzObj became true&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
    click the button multiple times, why does the second console keep firing?
&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;h4&amp;gt;&lt;/span&gt;isForRealz: {isForRealz &lt;span class="err"&gt;&amp;amp;&amp;amp;&lt;/span&gt; isForRealzObj.value}&lt;span class="nt"&gt;&amp;lt;/h4&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;on:click=&lt;/span&gt;&lt;span class="s"&gt;{makeTrue}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;click and watch the console&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you keep clicking the button while observing the console, you would notice that the &lt;code&gt;if&lt;/code&gt; statement behaves differently for a primitive and for an object. Which behaviour is more correct? It depends on your use case I guess but if you refactor from one to the other get ready for a surprise.&lt;br&gt;
For primitives it compares by value, and won't run again as long as the value didn't change.&lt;br&gt;&lt;br&gt;
For objects you would be tempted to think that it is a new object every time and Svelte simply compares by reference, but that doesn't seem to apply here because when we assign using &lt;code&gt;isForRealzObj.value = true;&lt;/code&gt; we are not creating a new object but updating the existing one, and the reference stays the same. &lt;/p&gt;
&lt;h4&gt;
  
  
  Solution:
&lt;/h4&gt;

&lt;p&gt;Well, just keep it in mind and be careful. This one is not that hard to watch for if you are aware of it. If you are using an object and don't want the block to run every time, you need to remember to put your own comparison with the old value in place and not run your logic if there was no change.&lt;/p&gt;
&lt;h3&gt;
  
  
  Gotcha #3: The evil micro-task (well, sometimes...)
&lt;/h3&gt;

&lt;p&gt;Alright, so far we were just warming up. This one comes in multiple flavours. I will demonstrate the two most common ones. You see, Svelte batches some operations (namely reactive blocks and DOM updates) and schedules them at the the end of the updates-queue - think requestAnimationFrame or setTimeout(0). This is called a &lt;code&gt;micro-task&lt;/code&gt; or &lt;code&gt;tick&lt;/code&gt;. One thing that is especially puzzling when you encounter it, is that asynchrony completely changes how things behave because it escapes the boundary of the micro-task. So switching between sync/ async operations can have all sorts of implications on how your code behaves. You might face infinite loops that weren't possible before (when going from sync to async) or face reactive blocks that stop getting triggered fully or partially (when going from async to sync). Let's look at some examples in which the way Svelte manages micro-tasks results in potentially unexpected behaviours.&lt;/p&gt;
&lt;h4&gt;
  
  
  3.1: Missing states
&lt;/h4&gt;

&lt;p&gt;How many times did the name change &lt;a href="https://svelte.dev/repl/3810c96a3d2746788a2f0dec4228eb75?version=3.43.1" rel="noopener noreferrer"&gt;here&lt;/a&gt;?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Sarah&lt;/span&gt;&lt;span class="dl"&gt;"&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;countChanges&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="nl"&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;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="s2"&gt;I run whenever the name changes!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;countChanges&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="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;John&lt;/span&gt;&lt;span class="dl"&gt;"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Another name that will be ignored?&lt;/span&gt;&lt;span class="dl"&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="s2"&gt;the name was indeed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Rose&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Hello {name}!&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
    I think that name has changed {countChanges} times
&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Svelte thinks that the answer is 1 while in reality it's 3. &lt;br&gt;
As I said above, reactive blocks only run at the end of the micro-task and only "see" the last state that existed at the time. &lt;strong&gt;In this sense it does not really live up to its name, "reactive"&lt;/strong&gt;, because it is not triggered every time a change takes place (in other words it is not triggered synchronously by a "set" operation on one of its dependencies as you might intuitively expect). &lt;/p&gt;
&lt;h4&gt;
  
  
  Solution to 3.1:
&lt;/h4&gt;

&lt;p&gt;When you need to track all state changes as they happen without missing any, use a &lt;a href="https://svelte.dev/tutorial/writable-stores" rel="noopener noreferrer"&gt;store&lt;/a&gt; instead. Stores update in real time and do not skip states. You can intercept the changes within the store's &lt;code&gt;set&lt;/code&gt; function or via subscribing to it directly (via &lt;code&gt;store.subscribe&lt;/code&gt;). &lt;a href="https://svelte.dev/repl/c584d584afd94aaf83d9fe698f8e264b?version=3.43.1" rel="noopener noreferrer"&gt;Here is how you would do it for the example above&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  3.2 - No recursion for you
&lt;/h4&gt;

&lt;p&gt;Sometimes you would want to have a reactive block that changes the values of its own dependencies until it "settles", in other words - good old recursion. &lt;a href="https://svelte.dev/repl/b417aec1edd94811ad87b9e0c039790d?version=3.43.1" rel="noopener noreferrer"&gt;Here is a somewhat contrived example&lt;/a&gt; for the sake of clarity, so you can see how this can go very wrong:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;isSmallerThan10&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&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;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nl"&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;count&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="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;count&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&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;span class="nx"&gt;console&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;smaller&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;count&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="c1"&gt;// this should trigger this reactive block again and enter the "else" but it doesn't&lt;/span&gt;
            &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt; 
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;console&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;larger&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;count&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;isSmallerThan10&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&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="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
    count is {count.a}
&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
    isSmallerThan10 is {isSmallerThan10}
&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;It doesn't matter whether &lt;code&gt;count&lt;/code&gt; is a primitive or an object, the &lt;code&gt;else&lt;/code&gt; part of the reactive block never runs and &lt;code&gt;isSmallerThan10&lt;/code&gt; goes out of sync and does so silently (it shows &lt;code&gt;true&lt;/code&gt; event though count is 11 and it should be &lt;code&gt;false&lt;/code&gt;).&lt;br&gt;
&lt;strong&gt;This happens because every reactive block can only ever run at most once per tick&lt;/strong&gt;.&lt;br&gt;
This specific issue has hit my team when we switched from an async store to an optimistically updating store, which made the application break in all sorts of subtle ways and left us totally baffled. Notice that this can also happen when you have multiple reactive blocks updating dependencies for each other in a loop of sorts.&lt;/p&gt;

&lt;p&gt;This behaviour can sometimes be considered a feature, that protects you from infinite loops, like &lt;a href="https://svelte.dev/repl/688edd30dd7a4c80ac834c04680b13ec?version=3.41.0" rel="noopener noreferrer"&gt;here&lt;/a&gt;, or even prevents the app from getting into an undesired state, like in &lt;a href="https://svelte.dev/repl/7379e0c1a3384ca3b3ab3c7572a7eaa1?version=3.43.1" rel="noopener noreferrer"&gt;this example&lt;/a&gt; that was kindly provided by Rich Harris.&lt;/p&gt;
&lt;h4&gt;
  
  
  Solution to 3.2: Forced asynchrony to the rescue
&lt;/h4&gt;

&lt;p&gt;In order to allow reactive blocks to run to resolution, you'll have to strategically place calls to &lt;a href="https://svelte.dev/tutorial/tick" rel="noopener noreferrer"&gt;tick()&lt;/a&gt; in your code. &lt;br&gt;
One extremely useful pattern (which I didn't come up with and can't take credit for) is&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="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;tick&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;then&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;//your code here&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://svelte.dev/repl/50c6864dd88c44289ed08efa41a6671d?version=3.43.1" rel="noopener noreferrer"&gt;Here is a fixed version&lt;/a&gt; of the &lt;code&gt;isSmallerThan10&lt;/code&gt; example using this trick.&lt;/p&gt;

&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;p&gt;I showed you the most common Svelte reactivity related gotchas, based on my team's experience, and some ways around them. &lt;br&gt;&lt;br&gt;
To me it seems that all frameworks and tools (at least the ones I've used to date) struggle to create a "gotchas free" implementation of reactivity. &lt;br&gt;&lt;br&gt;
I still prefer Svelte's flavour of reactivity over everything else I've tried to date, and hope that some of these issues would be addressed in the near future or would at least be better documented. &lt;br&gt;&lt;br&gt;
I guess it is inevitable that when using any tool to write production grade apps, one has to understand the inner workings of the tool in great detail in order to keep things together and Svelte is no different. &lt;br&gt; &lt;br&gt;
Thanks for reading and happy building! &lt;br&gt;&lt;br&gt;
If you encountered any of these gotchas in your apps or anything other gotchas I didn't mention, please do share in the comments. &lt;/p&gt;

</description>
      <category>svelte</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
