<?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: Onur Cinar</title>
    <description>The latest articles on DEV Community by Onur Cinar (@onurcinar).</description>
    <link>https://dev.to/onurcinar</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2455507%2F3aa78de4-9412-4988-b03a-d64d419c7f0a.jpeg</url>
      <title>DEV Community: Onur Cinar</title>
      <link>https://dev.to/onurcinar</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/onurcinar"/>
    <language>en</language>
    <item>
      <title>Herding AI Agents: A Weekend with Herdr, Tmux, and Remote Fleets</title>
      <dc:creator>Onur Cinar</dc:creator>
      <pubDate>Sun, 16 Aug 2026 18:33:50 +0000</pubDate>
      <link>https://dev.to/onurcinar/herding-ai-agents-a-weekend-with-herdr-tmux-and-remote-fleets-4cb8</link>
      <guid>https://dev.to/onurcinar/herding-ai-agents-a-weekend-with-herdr-tmux-and-remote-fleets-4cb8</guid>
      <description>&lt;p&gt;I like making life harder for myself most of the time (check out &lt;a href="https://zdo.com/blog/posts/optimizing-lenovo-duet-chromebook-for-development/" rel="noopener noreferrer"&gt;Pushing the Limits: Turning a 4GB Lenovo Duet Chromebook into My Primary Development Machine&lt;/a&gt;). As AI agents have gotten better, to the point where you can actually delegate work and let them run without steering every few minutes, you start getting bored. Then you start tackling multiple things at once, which means multiple agents running around in parallel doing different tasks. After all, compute is cheap and agent labor is even cheaper, so you can tackle projects you'd normally avoid as an engineer: converting a legacy codebase from Go to Rust, rewriting Vue into React, and so on. These are massive refactoring jobs you'd typically only undertake with a very compelling reason (like Microsoft &lt;a href="https://devblogs.microsoft.com/typescript/typescript-native-port/" rel="noopener noreferrer"&gt;porting TypeScript to Go&lt;/a&gt;), but with AI agents doing the heavy lifting, why not?&lt;/p&gt;

&lt;p&gt;Running multiple agents is easy on paper: open up a bunch of terminal windows, fire them up, and you're good to go. The challenge begins when you step away from your desk and wonder what they're up to. Sure, you could pay extra for hosted cloud platforms or mobile clients from AI providers, but that's expensive and too easy. Per paragraph one, I like making life harder for myself, so I'm not paying for that anyway. Naturally, the self-hosted solution is running them inside a terminal multiplexer like &lt;a href="https://github.com/tmux/tmux" rel="noopener noreferrer"&gt;tmux&lt;/a&gt; or &lt;a href="https://zellij.dev" rel="noopener noreferrer"&gt;Zellij&lt;/a&gt;. My muscle memory is hardwired to tmux, and I navigate panes without thinking. Zellij is great too (especially with its web support), and I've tried switching a few times.&lt;/p&gt;

&lt;p&gt;My typical workflow is running these agents on a machine at home. When I'm out, I use Tailscale to dial back into my home network, SSH in, and attach to the running sessions. It works, but the trouble with tmux is that it isn't agent-aware. You have to constantly cycle through windows and panes just to check if an agent is still working, crashed, or waiting for confirmation.&lt;/p&gt;

&lt;p&gt;This weekend, I gave &lt;a href="https://herdr.dev" rel="noopener noreferrer"&gt;Herdr&lt;/a&gt; a spin, a terminal multiplexer built specifically for running AI coding agents.&lt;/p&gt;

&lt;p&gt;Herdr organizes your screen cleanly from the get-go. You have workspaces to switch between different projects, and underneath, a live overview of all your running AI agents and their current state (working, idle, blocked, or done). From there, you can jump straight into specific tabs and panes to interact with whichever agent needs input. Herdr is clever enough to detect what agents are doing out of the box, but it also provides direct integrations (via a local socket API) so agents can report their status directly instead of the multiplexer having to guess.&lt;/p&gt;

&lt;h3&gt;
  
  
  On the road: Remote client mode
&lt;/h3&gt;

&lt;p&gt;Just like tmux, Herdr runs as a persistent server daemon in the background, so you can keep everything running on your home rig and reconnect on the go.&lt;/p&gt;

&lt;p&gt;One neat capability here is its native remote client mode. Instead of just doing a raw SSH session and attaching to a remote terminal, you can run &lt;code&gt;herdr --remote user@host&lt;/code&gt;. If you're on a laptop away from home, your local Herdr installation acts as a thin client talking directly to the remote Herdr server over SSH.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stopping the compulsive status-checking
&lt;/h3&gt;

&lt;p&gt;The biggest win for this kind of mobile workflow is that you don't have to keep checking your phone like a nervous parent. Because Herdr actually understands agent states (knowing when an agent transitions from &lt;code&gt;working&lt;/code&gt; to &lt;code&gt;blocked&lt;/code&gt; or &lt;code&gt;done&lt;/code&gt;), you can take advantage of notification plugins or webhooks (like Telegram alerts). When an agent hits a prompt and sits there waiting for your &lt;code&gt;[y/N]&lt;/code&gt; confirmation, you get pinged. You jump in via Tailscale, unblock it, and get back to your life.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's next: Letting agents drive the multiplexer?
&lt;/h3&gt;

&lt;p&gt;I've also heard, though I haven't ventured into this rabbit hole yet, that the Unix socket API goes both ways. In theory, agents can actually drive Herdr themselves: programmatically opening new panes, spinning up sub-agents for subtasks, and managing their own workspaces. A fleet of autonomous agents dynamically reconfiguring their own terminal environment sounds both wildly futuristic and slightly terrifying for my CPU fans, but it's on my radar to try.&lt;/p&gt;

&lt;h3&gt;
  
  
  Final thoughts
&lt;/h3&gt;

&lt;p&gt;While it's fundamentally still a multiplexer with some new keybindings to get used to (though you can customize them in &lt;code&gt;herdr.toml&lt;/code&gt;), it feels significantly better suited for multi-agent workflows. Instead of manually polling terminal panes to see who needs help, having that top-level agent awareness built into your multiplexer makes managing a fleet of agents feel manageable without having to hack together custom status scripts.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>terminal</category>
      <category>development</category>
    </item>
    <item>
      <title>Pushing the Limits: Turning a 4GB Lenovo Duet Chromebook into My Primary Development Machine</title>
      <dc:creator>Onur Cinar</dc:creator>
      <pubDate>Wed, 12 Aug 2026 05:35:42 +0000</pubDate>
      <link>https://dev.to/onurcinar/pushing-the-limits-turning-a-4gb-lenovo-duet-chromebook-into-my-primary-development-machine-3kao</link>
      <guid>https://dev.to/onurcinar/pushing-the-limits-turning-a-4gb-lenovo-duet-chromebook-into-my-primary-development-machine-3kao</guid>
      <description>&lt;p&gt;While everyone is pushing the limits of AI, I've been busy pushing the limits of a Lenovo Duet Chromebook tablet I inherited from my son. He used it as a school tablet for a year, but eventually got frustrated with its performance under their heavy workload. The tablet comes with just 4GB of RAM, but as a Chromebook, it has the unique ability to run both Android and Linux software.&lt;/p&gt;

&lt;p&gt;I've been trying to move to a portable, lightweight development machine for the past 5+ years. After experimenting with (and eventually giving up on) Android and Termux paired with a Bluetooth keyboard, I decided to give this Duet a serious shot. This article covers my journey optimizing this 4GB tablet into my primary development machine—one that I use both on the go as a tablet and docked via USB-C to a larger monitor, keyboard, and mouse.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 1: Ditching the Android VM (ARC++)
&lt;/h3&gt;

&lt;p&gt;The very first thing I did was disable the Android VM. I simply didn't have a use for Android apps on a dev setup. Disabling Android (ARC++) reclaims about &lt;strong&gt;1GB of RAM immediately&lt;/strong&gt;, which is a massive 25% gain on a 4GB device.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;One minor hiccup:&lt;/em&gt; I briefly regretted this when it broke my Android-based Tailscale configuration. Luckily, I solved this by running Tailscale directly inside the Linux container using its userspace networking mode and SOCKS5 proxy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tailscaled &lt;span class="nt"&gt;--tun&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;userspace-networking &lt;span class="nt"&gt;--socks5-server&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;localhost:1055
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Step 2: Fine-Tuning Chrome Flags &amp;amp; Settings
&lt;/h3&gt;

&lt;p&gt;Next, I tweaked a specific set of ChromeOS flags to maximize memory savings, force GPU acceleration across both Chrome and the Linux container, and stop Chrome from wasting CPU cycles and RAM prefetching pages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Memory Saver Mode (&lt;code&gt;chrome://settings/performance&lt;/code&gt;)&lt;/strong&gt;: Set to &lt;strong&gt;Maximum&lt;/strong&gt;. Automatically discards inactive background tabs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;#ignore-gpu-blocklist&lt;/code&gt; → Enabled&lt;/strong&gt;: Overrides built-in software rendering blocklists to force GPU hardware acceleration across the OS.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;#enable-gpu-rasterization&lt;/code&gt; → Enabled&lt;/strong&gt;: Uses the Mali GPU to rasterize web content instead of the ARM CPU.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;#enable-zero-copy&lt;/code&gt; → Enabled&lt;/strong&gt;: Forces raster threads to write directly to GPU memory tiles, bypassing staging buffers in RAM.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;#crostini-gpu-support&lt;/code&gt; → Enabled&lt;/strong&gt;: Ensures hardware GPU acceleration is passed through directly into the Linux container.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;#scheduler-configuration&lt;/code&gt; → Enables Hyper-Threading on relevant CPUs&lt;/strong&gt;: Tunes CPU thread scheduling to maximize performance for active workloads.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;#enable-parallel-downloading&lt;/code&gt; → Enabled&lt;/strong&gt;: Accelerates download speeds by splitting files into parallel chunks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;#prerender2&lt;/code&gt; &amp;amp; &lt;code&gt;#prerender2-cross-origin-iframes&lt;/code&gt; → Disabled&lt;/strong&gt;: Prevents Chrome from speculatively pre-loading linked pages and third-party iframe embeds in hidden background processes. This saves &lt;strong&gt;100MB–250MB of RAM&lt;/strong&gt; and stops background CPU spikes while browsing.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Step 3: Optimizing Virtual Memory &amp;amp; Crosh Swap
&lt;/h3&gt;

&lt;p&gt;ChromeOS uses ZRAM (compressed memory swap). By default, Linux container swappiness is set higher than ideal, causing active terminal tools to get pushed into swap too early.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Crosh ZRAM Swap&lt;/strong&gt;: Opened Crosh (&lt;code&gt;Ctrl&lt;/code&gt; + &lt;code&gt;Alt&lt;/code&gt; + &lt;code&gt;T&lt;/code&gt;) and expanded the ZRAM swap limit to 8GB to give the system plenty of breathing room during heavy multitasking:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   swap &lt;span class="nb"&gt;enable &lt;/span&gt;8192
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Linux Swappiness&lt;/strong&gt;: Created &lt;code&gt;/etc/sysctl.d/99-custom.conf&lt;/code&gt; inside Linux and added:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;   &lt;span class="py"&gt;vm.swappiness&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;20&lt;/span&gt;
   &lt;span class="py"&gt;vm.vfs_cache_pressure&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;150&lt;/span&gt;
   &lt;span class="py"&gt;vm.overcommit_memory&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps active CLI processes in physical RAM while allowing smooth swapping when under memory pressure.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 4: Streamlining the Linux Container (Headless CLI Mode)
&lt;/h3&gt;

&lt;p&gt;Since I don't use Linux GUI apps or Linux audio, I wanted to strip out background overhead.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Crucial Discovery regarding &lt;code&gt;sommelier&lt;/code&gt;&lt;/strong&gt;: I initially tried to disable &lt;code&gt;sommelier&lt;/code&gt; (the Wayland/X11 proxy display service), but the terminal crashed and Termina failed to start. In ChromeOS, &lt;code&gt;sommelier&lt;/code&gt; manages the host-to-container IPC sockets. &lt;strong&gt;Leave &lt;code&gt;sommelier&lt;/code&gt; running!&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Masking PipeWire &amp;amp; PackageKit&lt;/strong&gt;: I tried disabling &lt;code&gt;pipewire&lt;/code&gt; and &lt;code&gt;packagekit&lt;/code&gt;, but they kept restarting. The trick in &lt;code&gt;systemd&lt;/code&gt; is that socket-activated services will automatically respawn whenever their socket is triggered. Using &lt;code&gt;mask&lt;/code&gt; instead of &lt;code&gt;disable&lt;/code&gt; permanently stops them:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  &lt;span class="c"&gt;# Mask PipeWire audio services &amp;amp; sockets&lt;/span&gt;
  systemctl &lt;span class="nt"&gt;--user&lt;/span&gt; stop pipewire.service pipewire-pulse.service wireplumber.service filter-chain.service pipewire.socket pipewire-pulse.socket 2&amp;gt;/dev/null
  systemctl &lt;span class="nt"&gt;--user&lt;/span&gt; mask pipewire.service pipewire-pulse.service wireplumber.service filter-chain.service pipewire.socket pipewire-pulse.socket 2&amp;gt;/dev/null

  &lt;span class="c"&gt;# Mask PackageKit (APT background update checker)&lt;/span&gt;
  &lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl stop packagekit 2&amp;gt;/dev/null
  &lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl mask packagekit 2&amp;gt;/dev/null
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;After this round of optimizations, I can happily say that I'm using the Lenovo Duet as my primary development machine. It easily handles AI harnesses, terminal code editors (like Neovim/Micro), and web browsing. &lt;/p&gt;

&lt;p&gt;If you prefer VS Code, I recommend running &lt;strong&gt;VS Code Server&lt;/strong&gt; inside Linux and accessing the editor interface directly through the Chrome browser:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;code serve-web
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you the full VS Code experience without incurring the heavy RAM overhead of the Linux GUI container stack!&lt;/p&gt;

</description>
      <category>linux</category>
      <category>development</category>
      <category>productivity</category>
      <category>lxc</category>
    </item>
    <item>
      <title>Self-Evolving Apps: Not Vibe, Live-Coding in Ruby with LLMs and Metaprogramming</title>
      <dc:creator>Onur Cinar</dc:creator>
      <pubDate>Wed, 12 Aug 2026 05:28:04 +0000</pubDate>
      <link>https://dev.to/onurcinar/self-evolving-apps-not-vibe-live-coding-in-ruby-with-llms-and-metaprogramming-56h1</link>
      <guid>https://dev.to/onurcinar/self-evolving-apps-not-vibe-live-coding-in-ruby-with-llms-and-metaprogramming-56h1</guid>
      <description>&lt;p&gt;Using AI to generate code for a new application is a familiar workflow today. But what if an application starts as a completely blank slate, learning on the job and writing its own implementation live as you call nonexistent methods?&lt;/p&gt;

&lt;p&gt;This concept of live-patching and zero-downtime execution isn't entirely new. Early in my career at &lt;strong&gt;Nortel Networks&lt;/strong&gt;, I have seen this with &lt;strong&gt;PROTEL&lt;/strong&gt; (PRocess Oriented TELepony language), a proprietary language designed for telecom switches. To achieve "five nines" (99.999%) availability, you couldn't simply take systems offline for deployments—code updates had to happen via live hot-patching. Later on, I thoroughly enjoyed similar live code reloading capabilities while working with &lt;strong&gt;Erlang&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Recently, as I spent more time with &lt;strong&gt;Ruby&lt;/strong&gt;, its rich metaprogramming capabilities got me thinking: &lt;em&gt;What if we start with an empty Ruby object, and as we call methods on it, it uses an LLM to write and evaluate its own code on the fly?&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The Blank Slate
&lt;/h2&gt;

&lt;p&gt;Let's start with a completely empty class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Dummy&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;LiveCode&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's fire up &lt;code&gt;irb&lt;/code&gt; (Ruby's REPL) and start interacting with our dummy object as if the methods already existed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;dummy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Dummy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;span class="n"&gt;dummy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, Ruby will complain because &lt;code&gt;Dummy&lt;/code&gt; doesn't have an &lt;code&gt;add&lt;/code&gt; method. However, the method name (&lt;code&gt;add&lt;/code&gt;) and its arguments (&lt;code&gt;1, 2&lt;/code&gt;) clearly communicate our intent. &lt;/p&gt;

&lt;p&gt;Ruby provides a built-in hook called &lt;code&gt;method_missing&lt;/code&gt; to catch calls to undefined methods. This is where we bring in AI. To interact with our LLM provider, we'll use the excellent &lt;a href="https://rubyllm.com/" rel="noopener noreferrer"&gt;RubyLLM&lt;/a&gt; gem.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Dynamic Method Generation via &lt;code&gt;method_missing&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;To make this behavior reusable across objects, we put our logic inside a &lt;code&gt;LiveCode&lt;/code&gt; base class that &lt;code&gt;Dummy&lt;/code&gt; inherits from.&lt;/p&gt;

&lt;p&gt;Here is our initial &lt;code&gt;LiveCode&lt;/code&gt; implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LiveCode&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;method_missing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="s2"&gt;"Missing method: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;method_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inspect&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inspect&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

    &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;~&lt;/span&gt;&lt;span class="no"&gt;PROMPT&lt;/span&gt;&lt;span class="sh"&gt;
      You are a Ruby code generator. A missing method `&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;method_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;` was called
      with the arguments: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;args&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="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;, keyword arguments: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;.

      Return ONLY valid Ruby code defining this method. Do not include markdown formatting.
      Example:
      def &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;method_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;(...)
        # implementation
      end
&lt;/span&gt;&lt;span class="no"&gt;    PROMPT&lt;/span&gt;

    &lt;span class="n"&gt;ruby_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;content&lt;/span&gt;

    &lt;span class="c1"&gt;# Evaluate the generated Ruby code directly on the instance's singleton class&lt;/span&gt;
    &lt;span class="n"&gt;singleton_class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class_eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ruby_code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Re-dispatch the original method call now that it exists!&lt;/span&gt;
    &lt;span class="nb"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  How It Works
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Interception&lt;/strong&gt;: When &lt;code&gt;dummy.add(1, 2)&lt;/code&gt; is called, &lt;code&gt;method_missing&lt;/code&gt; intercepts the call and extracts the method name (&lt;code&gt;:add&lt;/code&gt;) and parameter types (&lt;code&gt;Integer, Integer&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LLM Prompting&lt;/strong&gt;: We construct a prompt instructing the model to return &lt;em&gt;only&lt;/em&gt; valid Ruby code defining the method.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Metaprogramming&lt;/strong&gt;: We use &lt;code&gt;singleton_class.class_eval(ruby_code)&lt;/code&gt; to inject the generated method into our object at runtime.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Re-dispatch&lt;/strong&gt;: Finally, &lt;code&gt;send(method_name, ...)&lt;/code&gt; invokes the newly defined method seamlessly!&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  3. Seeing It in Action
&lt;/h2&gt;

&lt;p&gt;Let's test this in IRB:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight irb"&gt;&lt;code&gt;&lt;span class="gp"&gt;irb(main):003&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dummy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="go"&gt;"Missing method: add, [1, 2], {}"
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="go"&gt;"Prompt: You are a Ruby code generator. A missing method `add` was called
with the arguments: [Integer, Integer], keyword arguments: [].
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="go"&gt;Return ONLY valid Ruby code defining this method. Do not include markdown formatting.
Example:
def add(...)
&lt;/span&gt;&lt;span class="c"&gt;  # implementation
&lt;/span&gt;&lt;span class="go"&gt;end
"
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="go"&gt;"Code: def add(a, b)
  a + b
end"
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In real-time, the LLM synthesized &lt;code&gt;def add(a, b); a + b; end&lt;/code&gt;, registered it on &lt;code&gt;dummy&lt;/code&gt;, executed it, and returned &lt;code&gt;3&lt;/code&gt;. Subsequent calls to &lt;code&gt;dummy.add(1, 2)&lt;/code&gt; will execute instantly without hitting &lt;code&gt;method_missing&lt;/code&gt; again!&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Adding Context: State and Inter-Method Dependencies
&lt;/h2&gt;

&lt;p&gt;A real object has multiple methods that need to share state via instance variables and interact with one another. To enable this, our LLM needs context about existing instance variables, their types, and previously generated methods.&lt;/p&gt;

&lt;p&gt;We introduce a helper method &lt;code&gt;llm_context&lt;/code&gt; to capture this state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;llm_context&lt;/span&gt;
  &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"# Current Instance Variables and Types:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;instance_variables&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;empty?&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;"(No instance variables yet)&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="nb"&gt;instance_variables&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;instance_variable_get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;" - &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;"# Previously Generated Methods:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@_generated_methods&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;nil?&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="vi"&gt;@_generated_methods&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;empty?&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;"(No generated methods yet)&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="vi"&gt;@_generated_methods&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;context&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We then update our prompt to include this rich context:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;~&lt;/span&gt;&lt;span class="no"&gt;PROMPT&lt;/span&gt;&lt;span class="sh"&gt;
  You are a Ruby code generator. A missing method `&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;method_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;` was called
  with the arguments: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;args&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="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;, keyword arguments: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;.

  Write ONLY the valid Ruby code to define `&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;method_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;`. Ensure it works
  well with the existing instance variables and previously generated methods.

  &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;llm_context&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;

  Return ONLY valid Ruby code defining this method. Do not include markdown formatting.
  Example:
  def &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;method_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;(...)
    # implementation
  end
&lt;/span&gt;&lt;span class="no"&gt;PROMPT&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  5. Stateful Walkthrough: Setters &amp;amp; Getters
&lt;/h2&gt;

&lt;p&gt;Let's test setting a property and then retrieving it in a subsequent call.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Setting a Value
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight irb"&gt;&lt;code&gt;&lt;span class="gp"&gt;irb(main):003&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dummy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Onur"&lt;/span&gt;
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="go"&gt;"Missing method: name=, [\"Onur\"], {}"
"Prompt: You are a Ruby code generator. A missing method `name=` was called
with the arguments: [String], keyword arguments: [].
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="go"&gt;Write ONLY the valid Ruby code to define `name=`. Ensure it works
well with the existing instance variables and previously generated methods.
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# Current Instance Variables and Types:
&lt;/span&gt;&lt;span class="go"&gt; - @chat: RubyLLM::Chat
&lt;/span&gt;&lt;span class="c"&gt;# Previously Generated Methods:
&lt;/span&gt;&lt;span class="go"&gt;(No generated methods yet)
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="go"&gt;Return ONLY valid Ruby code defining this method. Do not include markdown formatting.
Example:
def name=(...)
&lt;/span&gt;&lt;span class="c"&gt;  # implementation
&lt;/span&gt;&lt;span class="go"&gt;end
"
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="go"&gt;"Code: def name=(value)
  @name = value
end"
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"Onur"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Calling &lt;code&gt;dummy.name = "Onur"&lt;/code&gt; dynamically created the setter method &lt;code&gt;name=(value)&lt;/code&gt; which initialized the &lt;code&gt;@name&lt;/code&gt; instance variable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Reading the Value
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight irb"&gt;&lt;code&gt;&lt;span class="gp"&gt;irb(main):004&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dummy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="go"&gt;"Missing method: name, [], {}"
"Prompt: You are a Ruby code generator. A missing method `name` was called
with the arguments: [], keyword arguments: [].
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="go"&gt;Write ONLY the valid Ruby code to define `name`. Ensure it works
well with the existing instance variables and previously generated methods.
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# Current Instance Variables and Types:
&lt;/span&gt;&lt;span class="go"&gt; - @chat: RubyLLM::Chat
 - @_generated_methods: Hash
 - @name: String
&lt;/span&gt;&lt;span class="c"&gt;# Previously Generated Methods:
&lt;/span&gt;&lt;span class="go"&gt;def name=(value)
  @name = value
end
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="go"&gt;Return ONLY valid Ruby code defining this method. Do not include markdown formatting.
Example:
def name(...)
&lt;/span&gt;&lt;span class="c"&gt;  # implementation
&lt;/span&gt;&lt;span class="go"&gt;end
"
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="go"&gt;"Code: def name
  @name
end"
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"Onur"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because our prompt included &lt;code&gt;@name: String&lt;/code&gt; and the previously defined &lt;code&gt;name=(value)&lt;/code&gt; method, the LLM understood the context and generated the exact matching getter method &lt;code&gt;def name; @name; end&lt;/code&gt;.&lt;/p&gt;




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

&lt;p&gt;This experiment demonstrates how easily Ruby's dynamic nature combines with LLMs to build self-assembling objects. There are a few natural next steps for expanding this idea—such as persisting the generated code to disk, adding sandboxing/security checks, or enabling feedback loops to auto-fix runtime errors.&lt;/p&gt;




&lt;h2&gt;
  
  
  Source Code &amp;amp; Examples
&lt;/h2&gt;

&lt;p&gt;The complete working source code for this post is available in the site repository:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://zdo.com/blog/posts/self-evolving-app-code/" rel="noopener noreferrer"&gt;https://zdo.com/blog/posts/self-evolving-app-code/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>ai</category>
      <category>llm</category>
      <category>metaprogramming</category>
    </item>
    <item>
      <title>Stop Choosing One AI Coding Assistant: How I Pair Gemini CLI and OpenCode for Better Code</title>
      <dc:creator>Onur Cinar</dc:creator>
      <pubDate>Sat, 02 May 2026 20:28:25 +0000</pubDate>
      <link>https://dev.to/onurcinar/stop-choosing-one-ai-coding-assistant-how-i-pair-gemini-cli-and-opencode-for-better-code-3op6</link>
      <guid>https://dev.to/onurcinar/stop-choosing-one-ai-coding-assistant-how-i-pair-gemini-cli-and-opencode-for-better-code-3op6</guid>
      <description>&lt;p&gt;If you’re like me, you’ve toggled between AI coding assistants trying to find the "best" one. Gemini generates features fast, while OpenCode’s models are good for catching edge cases. But why choose?&lt;/p&gt;

&lt;p&gt;I built a custom workflow using &lt;strong&gt;Gemini CLI&lt;/strong&gt; to orchestrate three specialized agents that bridge these two worlds. Here’s how I get the best of both: Gemini's speed for implementation and OpenCode's rigor for review.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Three-Agent Setup
&lt;/h2&gt;

&lt;p&gt;My &lt;code&gt;.agents&lt;/code&gt; directory contains three distinct roles. The magic of Gemini CLI is its ability to not only write code but also manage other CLIs and agents:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;code-writer&lt;/strong&gt; (Gemini-powered): The primary builder. It handles the heavy lifting of implementation and iterates on feedback.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;opencode-code-reviewer&lt;/strong&gt; (Gemini-powered): The "Bridge Agent." This Gemini agent knows how to run the &lt;code&gt;opencode&lt;/code&gt; CLI, capture its feedback, and hand it back to the writer.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;code-reviewer&lt;/strong&gt; (OpenCode-powered): The "Expert Reviewer." This is the native agent inside OpenCode that provides the actual technical critique.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Workflow (Step by Step)
&lt;/h2&gt;

&lt;p&gt;This setup allows me to move from an issue to a verified PR with just two main commands:&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Implementation
&lt;/h3&gt;

&lt;p&gt;I start by asking Gemini to implement the feature:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Use the code-writer to implement ISSUE-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;code-writer&lt;/code&gt; generates the initial code, runs local tests, and ensures everything is idiomatic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: The Cross-Model Bridge
&lt;/h3&gt;

&lt;p&gt;Next, I trigger the review. This is where it gets interesting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Use the opencode-code-reviewer to review the 
changes and ask code-writer to address them.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Under the hood, the &lt;strong&gt;Bridge Agent&lt;/strong&gt; does the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Executes &lt;code&gt;opencode run --agent code-reviewer&lt;/code&gt; to get a deep-dive analysis.&lt;/li&gt;
&lt;li&gt; Captures the feedback (Status, Summary, Action Items).&lt;/li&gt;
&lt;li&gt; Invokes the &lt;code&gt;code-writer&lt;/code&gt; again, passing the OpenCode feedback as the new instructions.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 3: Iterate until Approved
&lt;/h3&gt;

&lt;p&gt;The loop repeats automatically or manually until the OpenCode reviewer returns an "APPROVED" status.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Works
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Model Diversity&lt;/strong&gt;: Different models have different blind spots. Having a Gemini agent write code and an OpenCode agent review it catches bugs that a single model might miss during self-review.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Automated Orchestration&lt;/strong&gt;: Gemini CLI handles the tool-calling and context-passing. You don't have to copy-paste code into different web UIs.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Specialization&lt;/strong&gt;: You use the best tool for each job.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Source Code
&lt;/h2&gt;

&lt;p&gt;Here is the core of the setup. You can drop these into your &lt;code&gt;.agents/&lt;/code&gt; folder and customize them for your own models.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. &lt;code&gt;.agents/code-writer.md&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;code-writer&lt;/span&gt;
&lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;write"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;edit"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bash"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="gh"&gt;# Code Writer Agent&lt;/span&gt;
You are an expert Google engineer. Implement features, write tests, and address feedback from the reviewer agents.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. &lt;code&gt;.agents/opencode-code-reviewer.md&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;opencode-code-reviewer&lt;/span&gt;
&lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;run_shell_command"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoke_agent"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="gh"&gt;# Bridge Agent&lt;/span&gt;
&lt;span class="p"&gt;1.&lt;/span&gt; Run: &lt;span class="sb"&gt;`opencode run --agent code-reviewer "Review changes..."`&lt;/span&gt;
&lt;span class="p"&gt;2.&lt;/span&gt; Capture output.
&lt;span class="p"&gt;3.&lt;/span&gt; Call &lt;span class="sb"&gt;`code-writer`&lt;/span&gt; with that output to fix any issues.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. &lt;code&gt;.agents/code-reviewer.md&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;code-reviewer&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="gh"&gt;# Expert Reviewer&lt;/span&gt;
You are an expert Google engineer. Provide a structured review with Status (APPROVED/CHANGES_REQUESTED), Summary, and Action Items.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;em&gt;Leveraging multiple AI tools via a single CLI changed how I build. It’s not about finding the "one" assistant; it's about building the right team.&lt;/em&gt;&lt;/p&gt;




</description>
      <category>ai</category>
      <category>cli</category>
      <category>productivity</category>
      <category>tooling</category>
    </item>
    <item>
      <title>The Self-Evolving AI Agent: How to Stop Correcting Your LLM Twice</title>
      <dc:creator>Onur Cinar</dc:creator>
      <pubDate>Sun, 19 Apr 2026 21:16:40 +0000</pubDate>
      <link>https://dev.to/onurcinar/the-self-evolving-ai-agent-how-to-stop-correcting-your-llm-twice-15kj</link>
      <guid>https://dev.to/onurcinar/the-self-evolving-ai-agent-how-to-stop-correcting-your-llm-twice-15kj</guid>
      <description>&lt;p&gt;We’ve all been there. You’re working on a lightweight Go microservice. You ask your AI agent to add a simple health-check endpoint.&lt;/p&gt;

&lt;p&gt;The agent responds: &lt;em&gt;"Sure! I'll just install the Gin framework and three middleware libraries..."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You stop it. &lt;em&gt;"No. This is a zero-dependency project. Use &lt;code&gt;net/http&lt;/code&gt; from the standard library."&lt;/em&gt; The agent apologizes, fixes the code, and you move on. But then comes tomorrow. You start a new session, ask for a logging utility, and—lo and behold—it tries to pull in &lt;code&gt;Zap&lt;/code&gt; or &lt;code&gt;Logrus&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Goldfish Effect has struck again.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In this article, I’ll show you how to move beyond static prompts and build an AI development environment that &lt;strong&gt;learns from its mistakes&lt;/strong&gt;. By leveraging native memory tools and the concept of "incremental self-evolution," we can force the agent to update its own project memory the moment a correction is made.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Case for "Zero-Dependency" Discipline
&lt;/h2&gt;

&lt;p&gt;Why does the "Zero-Dependency" rule matter? It’s the ultimate test for an AI. Most LLMs are trained on vast amounts of boilerplate code that relies on popular frameworks. Their "instinct" is to &lt;code&gt;go get&lt;/code&gt; the world.&lt;/p&gt;

&lt;p&gt;If you are building a high-performance tool or a secure utility, you want to keep your &lt;code&gt;go.mod&lt;/code&gt; clean - like how I am doing it with my side projects &lt;a href="https://github.com/cinar/indicator" rel="noopener noreferrer"&gt;Indicator&lt;/a&gt; and &lt;a href="https://github.com/cinar/resile" rel="noopener noreferrer"&gt;Resile&lt;/a&gt;. When you force an agent to use the standard library, you aren't just saving disk space; you're enforcing a specific architectural philosophy.&lt;/p&gt;

&lt;p&gt;The goal is to make that philosophy &lt;strong&gt;sticky&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Manual (and Flawed) Way: The End-of-Session Audit
&lt;/h2&gt;

&lt;p&gt;Before we automate this, let's look at how most developers handle this today. At the end of a long coding session, you realize you've corrected the agent half a dozen times. To ensure it doesn't happen again, you might manually ask for an audit:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You:&lt;/strong&gt; &lt;em&gt;"Summarize everything you learned about my preferences today and save it to GEMINI.md."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The agent might then produce something like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prefers &lt;code&gt;net/http&lt;/code&gt; over frameworks like Gin.&lt;/li&gt;
&lt;li&gt;Uses &lt;code&gt;camelCase&lt;/code&gt; for all internal helper functions.&lt;/li&gt;
&lt;li&gt;Always include a &lt;code&gt;README.md&lt;/code&gt; update for new features.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This works, but it's fragile. You have to remember to do it. If you're tired or in a rush, you skip the audit. The next morning, you're right back to square one, correcting the same mistakes. It adds friction to the very tool meant to reduce it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fix: The Proactive Memory Directive
&lt;/h2&gt;

&lt;p&gt;Instead of waiting until the end of a session to "audit" what happened—which breaks your focus and interrupts your flow—you want the agent to be proactive. You don't want to tell the agent what it learned; you want it to &lt;strong&gt;decide&lt;/strong&gt; what was important based on your feedback in real-time.&lt;/p&gt;

&lt;p&gt;Assuming you already use a &lt;code&gt;GEMINI.md&lt;/code&gt; file (or a similar local context file) for your projects, the secret is explicitly authorizing the agent to use its built-in &lt;code&gt;save_memory&lt;/code&gt; tool autonomously. &lt;/p&gt;

&lt;p&gt;By putting a strict directive at the top of your project's memory file, the agent knows it is responsible for its own evolution:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Whenever I correct your behavior, establish a new architectural constraint, or express a coding preference (e.g., 'no dependencies'), you MUST immediately use your &lt;code&gt;save_memory&lt;/code&gt; tool to persist this rule."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now, when you correct the agent about that Gin framework, it doesn't just apologize. It silently triggers its tool, updates &lt;code&gt;GEMINI.md&lt;/code&gt; with the new constraint, and &lt;em&gt;then&lt;/em&gt; writes your code. &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%2Fmbs05um0npjvsf7yiz62.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%2Fmbs05um0npjvsf7yiz62.png" alt=" " width="800" height="349"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By putting the burden of synthesis on the AI in real-time, it picks up on nuances you didn't even realize you were enforcing, and it does so seamlessly.&lt;/p&gt;

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

&lt;p&gt;If you're still correcting your AI's basic mistakes every morning, you're treating it like a calculator when you should be treating it like an apprentice.&lt;/p&gt;

&lt;p&gt;We are moving away from "Chatting with AI" and toward &lt;strong&gt;Orchestrating AI Ecosystems&lt;/strong&gt;. By giving your agent a mandate to remember what happened today, it stops being a generic assistant and starts acting like a teammate who has been on the project for months.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How are you handling agent memory? Are you still copying and pasting instructions, or have you set up incremental self-evolution in your project? Let's discuss in the comments.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>agents</category>
      <category>cli</category>
    </item>
    <item>
      <title>Bringing Claude's "Dispatch" Experience to Gemini and OpenCode</title>
      <dc:creator>Onur Cinar</dc:creator>
      <pubDate>Sun, 12 Apr 2026 22:19:26 +0000</pubDate>
      <link>https://dev.to/onurcinar/bringing-claudes-dispatch-experience-to-gemini-and-opencode-3pef</link>
      <guid>https://dev.to/onurcinar/bringing-claudes-dispatch-experience-to-gemini-and-opencode-3pef</guid>
      <description>&lt;p&gt;Claude’s "Dispatch" feature nailed the mobile-to-desktop UX. Being able to pull out your phone, delegate a heavy refactoring task to your local machine, and monitor its progress asynchronously is a massive quality-of-life upgrade. &lt;/p&gt;

&lt;p&gt;But if your daily drivers are CLI-native AI tools like Gemini or OpenCode, you might feel locked out of that seamless remote workflow. Because these tools run in your terminal rather than a proprietary desktop app, they lack a native mobile bridge. &lt;/p&gt;

&lt;p&gt;You don't have to abandon your favorite CLI tools to get that experience. By combining &lt;strong&gt;&lt;a href="https://tailscale.com/" rel="noopener noreferrer"&gt;Tailscale&lt;/a&gt;&lt;/strong&gt; and the modern terminal multiplexer &lt;strong&gt;&lt;a href="https://zellij.dev/" rel="noopener noreferrer"&gt;Zellij&lt;/a&gt;&lt;/strong&gt;, you can build a universal "Dispatch" layer. &lt;/p&gt;

&lt;p&gt;The best part? You aren't just sending a fire-and-forget command. You get the exact same interactive, conversational experience on your phone as you do sitting at your mechanical keyboard.&lt;/p&gt;

&lt;p&gt;Here is how to set up your own sovereign, mobile-to-local AI command center.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Missing Link: Zellij Web + Tailscale
&lt;/h2&gt;

&lt;p&gt;The core magic of Claude Dispatch is simply a secure, persistent, remotely accessible session. We can replicate this entirely using open-source infrastructure.&lt;/p&gt;

&lt;p&gt;To bridge the gap between your smartphone browser and your local desktop terminal, we need two components:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Tailscale:&lt;/strong&gt; This creates a secure overlay network. We will use &lt;strong&gt;Tailscale&lt;/strong&gt; to safely pipe a local port out to your devices.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Zellij:&lt;/strong&gt; This is the crucial piece. Zellij is a Rust-based terminal multiplexer with a robust &lt;strong&gt;Web Client&lt;/strong&gt;. Unlike SSH apps, which can be clunky on mobile, Zellij renders a fully responsive terminal UI directly in your mobile browser.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Setting Up Your "Dispatch" Server
&lt;/h2&gt;

&lt;p&gt;On your primary development machine—where your code, compilers, and AI tools live—you need to prepare the web session. Zellij takes privacy seriously, so the web client requires an authentication token and binds strictly to &lt;code&gt;localhost&lt;/code&gt; by default.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Create the Authentication Token
&lt;/h3&gt;

&lt;p&gt;Before starting the web UI, generate your secure login token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;zellij web &lt;span class="nt"&gt;--create-token&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Make sure to copy and save the outputted token. You will need it to authenticate when you connect from your smartphone.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Start the Zellij Web Server
&lt;/h3&gt;

&lt;p&gt;Next, start the web server and specify the port you want to use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;zellij web &lt;span class="nt"&gt;--port&lt;/span&gt; 4000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Expose the Port via Tailscale
&lt;/h3&gt;

&lt;p&gt;Because Zellij is safely listening only on localhost, you cannot reach it from your phone yet. Instead of exposing this to the public internet, we use Tailscale Serve to proxy that local port exclusively to your private Tailnet.&lt;/p&gt;

&lt;p&gt;Run this in a new terminal tab:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tailscale serve &lt;span class="nt"&gt;--bg&lt;/span&gt; &lt;span class="nt"&gt;--https&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;4000 localhost:4000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Remote Workflow in Action
&lt;/h2&gt;

&lt;p&gt;The real power of this setup is the seamless handoff. You don't need to craft complex, single-shot prompt strings. The interaction is identical to typing directly into your desktop CLI.&lt;/p&gt;

&lt;p&gt;Imagine you are deep into building a Go project. You are sitting at your desk, iterating on some validation logic with OpenCode or Gemini open in your terminal. You realize you need to leave the house, but the task isn't done.&lt;/p&gt;

&lt;p&gt;Here is how the dispatch workflow plays out:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The Mobile Handoff
&lt;/h3&gt;

&lt;p&gt;While waiting in line for coffee, you open Chrome or Safari on your phone and navigate to your Tailscale URL (&lt;code&gt;https://my-dev-box.domain.ts.net:4000&lt;/code&gt;). &lt;/p&gt;

&lt;p&gt;After pasting in your authentication token, you are instantly dropped right back into your active desktop terminal. You see the exact same interactive AI prompt you were looking at on your monitor moments ago.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Chat and Dispatch
&lt;/h3&gt;

&lt;p&gt;Because you are in a live, interactive session, you just talk to the CLI naturally. You type into your phone:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"I need to head out for a bit. Can you run the tests for the checker package, figure out why the struct validation is failing, and apply the fix?"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The AI acknowledges the request and begins its loop—reading your local files, executing &lt;code&gt;go test&lt;/code&gt;, and analyzing the output.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Detach and Walk Away
&lt;/h3&gt;

&lt;p&gt;This is the "Dispatch" moment. You simply close your mobile browser tab and put your phone in your pocket. &lt;/p&gt;

&lt;p&gt;Because Zellij is managing the session natively on your local hardware, the AI continues to run uninterrupted. It has full access to your local environment to do the heavy lifting.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Asynchronous Monitoring
&lt;/h3&gt;

&lt;p&gt;Check back 20 minutes later. Reopen the URL on your phone, and your terminal state is exactly how you left it. &lt;/p&gt;

&lt;p&gt;If the AI successfully refactored the code and the tests are green, the output is waiting for you. If it ran into a file-permission error, or if OpenCode paused to ask, &lt;em&gt;"Do you want me to commit these changes?"&lt;/em&gt;, the interactive prompt is right there in your mobile browser, patiently waiting for your reply.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. The Seamless Return to Desktop
&lt;/h3&gt;

&lt;p&gt;When you finally get back home, the magic of Zellij really shines. You don't have to sync anything, pull down remote cloud changes, or wonder what the AI did while you were gone. You simply sit down at your physical monitor, attach to the running Zellij session, and pick up exactly where you left off. The AI's responses, the shell history, and the code changes are all right there waiting for you.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Approach Scales
&lt;/h2&gt;

&lt;p&gt;Retrofitting your existing AI workflow with Zellij and Tailscale doesn't just mimic Claude Dispatch; it arguably surpasses it for power users.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Agnostic Architecture:&lt;/strong&gt; You aren't locked into one provider's ecosystem. You can use this exact workflow for Gemini, OpenCode, or any future terminal-based AI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frictionless UI:&lt;/strong&gt; You don't need a dedicated mobile app or complex SSH key management on your phone. Any modern web browser becomes a window into your live terminal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unrestricted Environment:&lt;/strong&gt; Your AI operates natively. It has full, unrestricted access to your actual development environment—your local databases, Docker containers, and raw file system—without needing to sync cloud workspaces.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By adding this networking layer, you transform your standard interactive CLIs from desktop-bound tools into true asynchronous agents that travel with you, keeping you in the loop wherever you are.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>cli</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Prioritize Your Traffic: Priority-Aware Bulkheads in Go</title>
      <dc:creator>Onur Cinar</dc:creator>
      <pubDate>Sun, 12 Apr 2026 16:00:00 +0000</pubDate>
      <link>https://dev.to/onurcinar/prioritize-your-traffic-priority-aware-bulkheads-in-go-2ain</link>
      <guid>https://dev.to/onurcinar/prioritize-your-traffic-priority-aware-bulkheads-in-go-2ain</guid>
      <description>&lt;p&gt;Not all traffic is created equal. When your system is under heavy load, should a background cleanup task compete for the same resources as a user's checkout request? &lt;/p&gt;

&lt;p&gt;In a standard bulkhead, the answer is often "yes"—the first 10 requests get in, and the 11th is rejected, regardless of its importance. This is where &lt;strong&gt;Priority-Aware Bulkheads&lt;/strong&gt; come in.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem: The "Fairness" Trap
&lt;/h2&gt;

&lt;p&gt;Standard bulkheads are fair. They treat every request the same. But in a real-world system, fairness can be a liability:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Critical Traffic&lt;/strong&gt;: User-facing requests (e.g., "Complete Purchase", "Login") that directly impact revenue or user experience.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Standard Traffic&lt;/strong&gt;: Regular API calls (e.g., "View Profile", "Search") that are important but not immediately critical.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Low-Priority Traffic&lt;/strong&gt;: Background tasks (e.g., "Generate Report", "Sync Analytics", "Cache Warming") that can be delayed or retried later.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When your system is at 90% capacity, you want to stop accepting "Generate Report" requests to ensure there's enough room for "Complete Purchase" calls. A standard bulkhead can't do this; it will fill up with whatever arrives first.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Solution: Priority-Aware Bulkheads
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;Priority-Aware Bulkhead&lt;/strong&gt; uses &lt;strong&gt;Load Shedding&lt;/strong&gt; based on priority levels. It defines utilization thresholds for different types of traffic. &lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Low Priority&lt;/strong&gt;: Allowed only if the bulkhead is less than 50% full.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Standard Priority&lt;/strong&gt;: Allowed only if the bulkhead is less than 80% full.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Critical Priority&lt;/strong&gt;: Allowed until the bulkhead is 100% full.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This ensures that your most important traffic always has a "buffer" of capacity reserved for it, even when the system is under significant pressure.&lt;/p&gt;




&lt;h2&gt;
  
  
  Implementing with Resile
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/cinar/resile" rel="noopener noreferrer"&gt;Resile&lt;/a&gt; provides a built-in &lt;code&gt;PriorityBulkhead&lt;/code&gt; that makes this pattern easy to implement.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Define Your Priorities
&lt;/h3&gt;

&lt;p&gt;Resile uses a simple &lt;code&gt;Priority&lt;/code&gt; type with three levels: &lt;code&gt;PriorityLow&lt;/code&gt;, &lt;code&gt;PriorityStandard&lt;/code&gt;, and &lt;code&gt;PriorityCritical&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;thresholds&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;resile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Priority&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;float64&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;resile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PriorityLow&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="m"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c"&gt;// Shed at 50% utilization&lt;/span&gt;
    &lt;span class="n"&gt;resile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PriorityStandard&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c"&gt;// Shed at 80% utilization&lt;/span&gt;
    &lt;span class="n"&gt;resile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PriorityCritical&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c"&gt;// Shed only when 100% full&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Create a bulkhead with a capacity of 20&lt;/span&gt;
&lt;span class="n"&gt;pb&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;resile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewPriorityBulkhead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;thresholds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Attach Priority to Context
&lt;/h3&gt;

&lt;p&gt;You communicate the importance of a request by attaching a priority to its &lt;code&gt;context.Context&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Create a context with Critical priority&lt;/span&gt;
&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;resile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithPriority&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;resile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PriorityCritical&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// Execute the action within the priority bulkhead&lt;/span&gt;
&lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;pb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;processOrder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Handle Shedded Load
&lt;/h3&gt;

&lt;p&gt;When a request is rejected because its priority threshold is exceeded, Resile returns &lt;code&gt;resile.ErrShedLoad&lt;/code&gt;. If the bulkhead is physically full (100% capacity), it returns &lt;code&gt;resile.ErrBulkheadFull&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrShedLoad&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// This low/standard priority request was shedded to save capacity &lt;/span&gt;
    &lt;span class="c"&gt;// for higher-priority traffic.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Why Use Priority-Aware Bulkheads?
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Protect the Critical Path&lt;/strong&gt;: Ensure that your most important business processes remain available even during traffic spikes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Graceful Degradation&lt;/strong&gt;: Instead of a total system failure, your service gracefully degrades by dropping non-essential background work first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Better User Experience&lt;/strong&gt;: Users performing critical actions see no slowdown, while background "noise" is managed behind the scenes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost Efficiency&lt;/strong&gt;: You don't need to over-provision your infrastructure to handle peak "background" load if you can simply shed it when necessary.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Comparison: Static vs. Priority vs. Adaptive
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Static Bulkhead&lt;/th&gt;
&lt;th&gt;Priority-Aware Bulkhead&lt;/th&gt;
&lt;th&gt;Adaptive Concurrency&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Limit Type&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Fixed (e.g., 20)&lt;/td&gt;
&lt;td&gt;Fixed + Thresholds&lt;/td&gt;
&lt;td&gt;Dynamic (Auto-tuned)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Traffic Awareness&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;None (All equal)&lt;/td&gt;
&lt;td&gt;High (Priority-based)&lt;/td&gt;
&lt;td&gt;None (All equal)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Best For&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Simple isolation&lt;/td&gt;
&lt;td&gt;Multi-tenant or Tiered apps&lt;/td&gt;
&lt;td&gt;Volatile environments&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://dev.to/onurcinar/stop-the-domino-effect-bulkhead-isolation-in-go-5cgl"&gt;Read more about Static Bulkheads&lt;/a&gt; or &lt;a href="https://dev.to/onurcinar/beyond-static-limits-adaptive-concurrency-with-tcp-vegas-in-go-3gne"&gt;Explore Adaptive Concurrency&lt;/a&gt;.&lt;/p&gt;




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

&lt;p&gt;Resilience isn't just about keeping the lights on; it's about keeping the &lt;em&gt;right&lt;/em&gt; lights on. Priority-Aware Bulkheads give you the surgical precision needed to manage your system's resources effectively during times of stress.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check out the full example:&lt;/strong&gt; &lt;a href="https://github.com/cinar/resile/tree/main/examples/prioritybulkhead" rel="noopener noreferrer"&gt;Priority Bulkhead Example&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Learn more about Resile:&lt;/strong&gt; &lt;a href="https://github.com/cinar/resile" rel="noopener noreferrer"&gt;github.com/cinar/resile&lt;/a&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>microservices</category>
      <category>distributedsystems</category>
      <category>backend</category>
    </item>
    <item>
      <title>Stopping the Zombie Requests: Distributed Deadline Propagation in Go</title>
      <dc:creator>Onur Cinar</dc:creator>
      <pubDate>Sat, 11 Apr 2026 15:43:15 +0000</pubDate>
      <link>https://dev.to/onurcinar/stopping-the-zombie-requests-distributed-deadline-propagation-in-go-3ccm</link>
      <guid>https://dev.to/onurcinar/stopping-the-zombie-requests-distributed-deadline-propagation-in-go-3ccm</guid>
      <description>&lt;p&gt;Imagine a common scenario in a microservice architecture: A user clicks a "Buy" button, triggering a request to &lt;strong&gt;Service A&lt;/strong&gt;. Service A calls &lt;strong&gt;Service B&lt;/strong&gt;, which in turn calls &lt;strong&gt;Service C&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Suddenly, Service A times out. The user sees an error message and refreshes the page. But &lt;strong&gt;Service B and Service C are still working&lt;/strong&gt; on the original request, consuming CPU, memory, and database connections for a result that will never be seen.&lt;/p&gt;

&lt;p&gt;These are &lt;strong&gt;Zombie Requests&lt;/strong&gt;. In a high-traffic system, they can lead to cascading failures and resource exhaustion, even if the underlying services are technically "healthy."&lt;/p&gt;

&lt;p&gt;To stop the zombies, you need &lt;strong&gt;Distributed Deadline Propagation&lt;/strong&gt;. Here is how to implement it effortlessly using &lt;a href="https://github.com/cinar/resile" rel="noopener noreferrer"&gt;Resile&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is Distributed Deadline Propagation?
&lt;/h2&gt;

&lt;p&gt;Deadlines are not just local timeouts. A deadline represents the &lt;strong&gt;absolute point in time&lt;/strong&gt; after which the entire request chain should be abandoned.&lt;/p&gt;

&lt;p&gt;Distributed Deadline Propagation is the process of:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Tracking&lt;/strong&gt; the remaining time (the "budget") as a request moves through the system.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Communicating&lt;/strong&gt; that budget to downstream services via metadata (like HTTP headers).&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Aborting early&lt;/strong&gt; if the remaining budget is too small to realistically complete the work.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  The Resile Way: Smart Deadlines
&lt;/h2&gt;

&lt;p&gt;Resile provides two powerful mechanisms to handle distributed deadlines: &lt;strong&gt;Early Abort&lt;/strong&gt; and &lt;strong&gt;Header Injection&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Early Abort: &lt;code&gt;WithMinDeadlineThreshold&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Why start a request if you only have 2 milliseconds left? The network latency alone will likely exceed that, and you'll just be wasting resources.&lt;/p&gt;

&lt;p&gt;Resile's &lt;code&gt;WithMinDeadlineThreshold&lt;/code&gt; allows you to define a "safety buffer." If the remaining time in the &lt;code&gt;context.Context&lt;/code&gt; is less than this threshold, Resile will &lt;strong&gt;abort the execution immediately&lt;/strong&gt; with a &lt;code&gt;context.DeadlineExceeded&lt;/code&gt; error, before even attempting the work.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"context"&lt;/span&gt;
    &lt;span class="s"&gt;"time"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/cinar/resile"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// Define a policy with a 10ms "Early Abort" threshold.&lt;/span&gt;
&lt;span class="n"&gt;policy&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;resile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewPolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;resile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithRetry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;resile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithMinDeadlineThreshold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Millisecond&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// If ctx has only 5ms left, this returns context.DeadlineExceeded instantly.&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Do&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;apiClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FetchData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Header Injection: &lt;code&gt;InjectDeadlineHeader&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;To propagate the deadline to downstream services, you need to "inject" the remaining time into your outgoing requests. Resile provides a transport-agnostic &lt;code&gt;InjectDeadlineHeader&lt;/code&gt; function that supports both standard HTTP and gRPC.&lt;/p&gt;

&lt;h4&gt;
  
  
  For REST/HTTP:
&lt;/h4&gt;

&lt;p&gt;You can inject the remaining milliseconds into a custom header (e.g., &lt;code&gt;X-Request-Timeout&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;FetchData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewRequestWithContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"http://service-b/data"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// Inject the remaining milliseconds into the header.&lt;/span&gt;
    &lt;span class="n"&gt;resile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InjectDeadlineHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"X-Request-Timeout"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;httpClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Do&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  For gRPC:
&lt;/h4&gt;

&lt;p&gt;Resile natively supports the standard &lt;code&gt;Grpc-Timeout&lt;/code&gt; header format, ensuring compatibility with the gRPC ecosystem.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;FetchData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;md&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;

    &lt;span class="c"&gt;// Inject using the gRPC-specific format (e.g., "100m" for 100ms).&lt;/span&gt;
    &lt;span class="n"&gt;resile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InjectDeadlineHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;md&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Grpc-Timeout"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewOutgoingContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;md&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grpcClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;pb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Why This Matters for Resilience
&lt;/h2&gt;

&lt;p&gt;Without distributed deadlines, your system is vulnerable to &lt;strong&gt;Resource Exhaustion Attacks&lt;/strong&gt;—not from malicious actors, but from your own retries and slow dependencies.&lt;/p&gt;

&lt;p&gt;By implementing propagation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;You save money:&lt;/strong&gt; You're not paying for cloud compute that produces "zombie" results.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;You prevent meltdowns:&lt;/strong&gt; Downstream services are protected from "retry storms" that they can't possibly satisfy in time.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;You improve UX:&lt;/strong&gt; Failures happen faster (Fail-Fast), allowing the UI to react or switch to a fallback immediately.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://dev.to/onurcinar/preventing-microservice-meltdowns-adaptive-retries-and-circuit-breakers-in-go-30ho"&gt;Read more: Preventing Meltdowns: How Adaptive Retries Protect Your Downstream&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Comparison: Static vs. Distributed Deadlines
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Static Timeouts&lt;/th&gt;
&lt;th&gt;Distributed Deadlines (Resile)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scope&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Single Service&lt;/td&gt;
&lt;td&gt;Entire Request Chain&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Awareness&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Blind to upstream delays&lt;/td&gt;
&lt;td&gt;Aware of the total "time budget"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Efficiency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;High waste (Zombie requests)&lt;/td&gt;
&lt;td&gt;Zero waste (Early Abort)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Protocol&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Internal only&lt;/td&gt;
&lt;td&gt;HTTP/gRPC compatible&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




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

&lt;p&gt;Resilience isn't just about making things "work"; it's about knowing when to &lt;strong&gt;stop working&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Distributed Deadline Propagation is the "social contract" of a microservice architecture. It ensures that every service in the chain is working towards a common goal—and respects the reality that sometimes, time simply runs out.&lt;/p&gt;

&lt;p&gt;With Resile, implementing this complex pattern becomes a matter of a few lines of configuration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Explore Resile on GitHub:&lt;/strong&gt; &lt;a href="https://github.com/cinar/resile" rel="noopener noreferrer"&gt;github.com/cinar/resile&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;How are you handling request budgets in your distributed systems? Let's discuss!&lt;/p&gt;

</description>
      <category>go</category>
      <category>microservices</category>
      <category>distributedsystems</category>
      <category>performance</category>
    </item>
    <item>
      <title>Native Chaos Engineering: Testing Resilience with Fault &amp; Latency Injection</title>
      <dc:creator>Onur Cinar</dc:creator>
      <pubDate>Fri, 03 Apr 2026 14:51:48 +0000</pubDate>
      <link>https://dev.to/onurcinar/native-chaos-engineering-testing-resilience-with-fault-latency-injection-83</link>
      <guid>https://dev.to/onurcinar/native-chaos-engineering-testing-resilience-with-fault-latency-injection-83</guid>
      <description>&lt;p&gt;You’ve implemented retries, circuit breakers, and timeouts. Your application is now "resilient." But how do you know these policies actually work? Waiting for a production meltdown to verify your configuration is a high-stakes gamble. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Native Chaos Engineering&lt;/strong&gt; in Resile allows you to synthetically induce failure and latency directly into your application's execution path, ensuring your resilience policies are battle-tested before they're ever needed in production.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem: "Dark Code" in Resilience Policies
&lt;/h2&gt;

&lt;p&gt;Resilience policies—like retries and circuit breakers—are often "dark code." These are execution paths that are rarely traversed under normal operating conditions. Because they only trigger during failure, they are notoriously difficult to test and prone to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Buggy Configurations&lt;/strong&gt;: A retry limit that is too high, or a circuit breaker threshold that never trips.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Unintended Side Effects&lt;/strong&gt;: A retry loop that accidentally consumes all available database connections.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Silent Failures&lt;/strong&gt;: A fallback strategy that actually panics because it hasn't been executed in months.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Traditional chaos engineering tools often operate at the infrastructure layer (e.g., killing pods or dropping network packets). While powerful, these tools can be difficult to set up in local development or staging environments and often lack the granularity to test specific application-level logic.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Solution: Fault &amp;amp; Latency Injection
&lt;/h2&gt;

&lt;p&gt;Resile provides a &lt;strong&gt;Chaos Injector&lt;/strong&gt; middleware that can be integrated directly into any execution policy. By injecting synthetic faults (errors) and latency (delays) with configurable probabilities, you can simulate various failure scenarios without touching your infrastructure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Features:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Deterministic Randomness&lt;/strong&gt;: Uses Go 1.22's &lt;code&gt;math/rand/v2&lt;/code&gt; for efficient and predictable random number generation.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Context-Aware&lt;/strong&gt;: Latency injection strictly respects &lt;code&gt;context.Context&lt;/code&gt; cancellation. If your request times out while Resile is injecting chaos latency, it exits immediately.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Zero Dependencies&lt;/strong&gt;: Just like the rest of the Resile core, the chaos package depends only on the Go standard library.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Granular Control&lt;/strong&gt;: Configure error and latency probabilities independently for fine-tuned simulation.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Practical Usage
&lt;/h2&gt;

&lt;p&gt;Integrating chaos into your existing Resile policies is as simple as adding the &lt;code&gt;WithChaos&lt;/code&gt; option.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Basic Chaos Configuration
&lt;/h3&gt;

&lt;p&gt;You can define a chaos configuration that injects a 10% error rate and adds 100ms of latency to 20% of requests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/cinar/resile"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/cinar/resile/chaos"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// Configure chaos injection&lt;/span&gt;
&lt;span class="n"&gt;cfg&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;chaos&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ErrorProbability&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="m"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                    &lt;span class="c"&gt;// 10% chance of failure&lt;/span&gt;
    &lt;span class="n"&gt;InjectedError&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"chaos!"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;   &lt;span class="c"&gt;// The error to return&lt;/span&gt;
    &lt;span class="n"&gt;LatencyProbability&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                    &lt;span class="c"&gt;// 20% chance of latency&lt;/span&gt;
    &lt;span class="n"&gt;LatencyDuration&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="m"&gt;100&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Millisecond&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c"&gt;// Delay to inject&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Apply it to an execution&lt;/span&gt;
&lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;resile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DoErr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;resile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithRetry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;resile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithChaos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Testing Your Circuit Breaker
&lt;/h3&gt;

&lt;p&gt;Chaos injection is exceptionally useful for verifying that your circuit breaker trips under pressure. By setting a high &lt;code&gt;ErrorProbability&lt;/code&gt;, you can force the breaker to transition from &lt;code&gt;Closed&lt;/code&gt; to &lt;code&gt;Open&lt;/code&gt; in a controlled environment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;cb&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;circuit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;circuit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;WindowSize&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;           &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;FailureRateThreshold&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c"&gt;// Force 80% error rate to trip the breaker quickly&lt;/span&gt;
&lt;span class="n"&gt;cfg&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;chaos&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ErrorProbability&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;InjectedError&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"synthetic failure"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;resile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DoErr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="n"&gt;resile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithCircuitBreaker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;resile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithChaos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cfg&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="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Circuit Breaker State: %v&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="c"&gt;// Should be Open&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Configuration Reference
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;chaos.Config&lt;/code&gt; struct provides the following options:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ErrorProbability&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float64&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The probability of injecting an error (0.0 to 1.0).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;InjectedError&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;error&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The error to be returned when an error is injected.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;LatencyProbability&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float64&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The probability of injecting latency (0.0 to 1.0).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;LatencyDuration&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;time.Duration&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The duration of the latency to be injected.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




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

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Environment Gating&lt;/strong&gt;: Never enable chaos injection in production unless you are performing a planned game day. Use environment variables to gate the configuration:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ENABLE_CHAOS"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;opts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithChaos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;loadChaosCfg&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;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Observability&lt;/strong&gt;: Ensure your &lt;code&gt;Instrumenter&lt;/code&gt; (like &lt;code&gt;slog&lt;/code&gt; or &lt;code&gt;OTel&lt;/code&gt;) is active. This allows you to see the injected errors and latencies in your logs and traces, making it easier to verify how your application responds.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Start Small&lt;/strong&gt;: Begin with low probabilities (e.g., 1-2%) to identify subtle race conditions or timeout issues before increasing the "blast radius."&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




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

&lt;p&gt;Resilience is not a "set it and forget it" feature. It requires continuous verification. By bringing chaos engineering directly into your application's execution policies, Resile empowers you to build systems that aren't just theoretically resilient, but practically battle-hardened.&lt;/p&gt;

&lt;p&gt;For more information and advanced usage, visit the &lt;a href="https://github.com/cinar/resile" rel="noopener noreferrer"&gt;github.com/cinar/resile&lt;/a&gt; project.&lt;/p&gt;

</description>
      <category>go</category>
      <category>testing</category>
      <category>sre</category>
      <category>distributedsystems</category>
    </item>
    <item>
      <title>Beyond Static Limits: Adaptive Concurrency with TCP-Vegas in Go</title>
      <dc:creator>Onur Cinar</dc:creator>
      <pubDate>Thu, 02 Apr 2026 19:11:51 +0000</pubDate>
      <link>https://dev.to/onurcinar/beyond-static-limits-adaptive-concurrency-with-tcp-vegas-in-go-3gne</link>
      <guid>https://dev.to/onurcinar/beyond-static-limits-adaptive-concurrency-with-tcp-vegas-in-go-3gne</guid>
      <description>&lt;p&gt;Traditional concurrency limits (like bulkheads) are static. You pick a number—say, 10 concurrent requests— and hope for the best. But in the dynamic world of cloud infrastructure, "10" might be too conservative when the network is fast, or dangerously high when a downstream service starts to queue.&lt;/p&gt;

&lt;p&gt;Static limits require manual tuning, which is often done &lt;em&gt;after&lt;/em&gt; an outage has already happened. To build truly resilient systems, we need &lt;strong&gt;Adaptive Concurrency Control&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here is how to implement dynamic concurrency limits in Go using &lt;a href="https://github.com/cinar/resile" rel="noopener noreferrer"&gt;Resile&lt;/a&gt;, inspired by the TCP-Vegas congestion control algorithm.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem: The "Fixed-Limit" Trap
&lt;/h2&gt;

&lt;p&gt;Imagine your service talks to a database. You've set a bulkhead limit of 50 concurrent connections. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Scenario A (Normal):&lt;/strong&gt; Database latency is 10ms. 50 concurrent requests mean you're handling 5,000 RPS. Everything is fine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scenario B (Degraded):&lt;/strong&gt; Database latency spikes to 500ms due to a background maintenance task. Your 50 "slots" are now filled with slow requests. Your throughput drops to 100 RPS, and new incoming requests start to pile up in your own service's memory, eventually leading to a cascade of failures.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In Scenario B, 50 is &lt;strong&gt;too many&lt;/strong&gt;. You're holding onto resources that are essentially waiting on a bottleneck. You should have reduced your concurrency limit to prevent your own service from becoming part of the problem.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Solution: Little's Law &amp;amp; TCP-Vegas
&lt;/h2&gt;

&lt;p&gt;Adaptive Concurrency uses two core principles:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Little's Law (&lt;em&gt;L = λW&lt;/em&gt;):&lt;/strong&gt; The number of items in a system (&lt;em&gt;L&lt;/em&gt;) is equal to the arrival rate (&lt;em&gt;λ&lt;/em&gt;) multiplied by the average time an item spends in the system (&lt;em&gt;W&lt;/em&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TCP-Vegas AIMD:&lt;/strong&gt; An Additive Increase, Multiplicative Decrease (AIMD) logic based on Round-Trip Time (RTT).&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  How it works:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Baseline:&lt;/strong&gt; The algorithm tracks the minimum RTT (the fastest the system can possibly go).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Additive Increase:&lt;/strong&gt; If current latency is close to the baseline (no queuing detected), it cautiously increases the concurrency limit by 1.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiplicative Decrease:&lt;/strong&gt; If latency spikes above a threshold (e.g., 1.5 x baseline), it assumes queuing is happening downstream and immediately slashes the concurrency limit by 20%.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This allows your service to automatically "breathe" with the network. It expands to use available capacity when things are fast and contracts instantly to protect itself when things slow down.&lt;/p&gt;




&lt;h2&gt;
  
  
  Implementing with Resile
&lt;/h2&gt;

&lt;p&gt;Resile makes it trivial to add adaptive concurrency to your Go services.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// 1. Create a shared AdaptiveLimiter.&lt;/span&gt;
&lt;span class="c"&gt;// This should be shared across multiple calls to the same resource.&lt;/span&gt;
&lt;span class="n"&gt;al&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;resile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewAdaptiveLimiter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c"&gt;// 2. Use it in your policy.&lt;/span&gt;
&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;resile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewPolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;resile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithAdaptiveLimiterInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;al&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// 3. Execute your action.&lt;/span&gt;
&lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DoErr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;callDownstreamService&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="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrShedLoad&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// The limiter has dynamically reduced the limit and shed this request&lt;/span&gt;
    &lt;span class="c"&gt;// to protect the system.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why "TCP-Vegas"?
&lt;/h3&gt;

&lt;p&gt;Unlike other congestion control algorithms (like TCP-Reno) that wait for packet loss to react, TCP-Vegas reacts to &lt;strong&gt;latency changes&lt;/strong&gt;. This is perfect for microservices where "packet loss" usually means a timed-out request or a 503 error—both of which we want to avoid &lt;em&gt;before&lt;/em&gt; they happen.&lt;/p&gt;




&lt;h2&gt;
  
  
  Zero-Configuration Resilience
&lt;/h2&gt;

&lt;p&gt;One of the biggest benefits of Adaptive Concurrency is that it requires &lt;strong&gt;zero manual configuration&lt;/strong&gt;. You don't need to know if your database can handle 50 or 500 connections. The &lt;code&gt;AdaptiveLimiter&lt;/code&gt; will discover the optimal limit in real-time.&lt;/p&gt;

&lt;p&gt;It even handles "Network Drift." Over time, the minimum baseline RTT is gradually decayed, allowing the system to recalibrate if you migrate your database to a faster region or if the network topology changes.&lt;/p&gt;




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

&lt;p&gt;Resilience isn't just about surviving failures; it's about &lt;strong&gt;adapting&lt;/strong&gt; to them. By moving from static bulkheads to adaptive concurrency, you're building a system that can intelligently protect itself from cascading failures while maximizing throughput during "peace time."&lt;/p&gt;

&lt;p&gt;Check out the &lt;a href="https://github.com/cinar/resile/tree/main/examples/adaptiveconcurrency" rel="noopener noreferrer"&gt;Adaptive Concurrency Example&lt;/a&gt; in the Resile repository to see it in action.&lt;/p&gt;

</description>
      <category>go</category>
      <category>distributedsystems</category>
      <category>sre</category>
      <category>microservices</category>
    </item>
    <item>
      <title>Respecting Boundaries: Precise Rate Limiting in Go</title>
      <dc:creator>Onur Cinar</dc:creator>
      <pubDate>Tue, 24 Mar 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/onurcinar/respecting-boundaries-precise-rate-limiting-in-go-lca</link>
      <guid>https://dev.to/onurcinar/respecting-boundaries-precise-rate-limiting-in-go-lca</guid>
      <description>&lt;p&gt;Traffic spikes are a double-edged sword. On one hand, you’re busy! On the other, those spikes can overwhelm your services or exceed your downstream quotas. &lt;/p&gt;

&lt;p&gt;Whether you're protecting your own database from an unexpected burst or respecting a third-party API’s strict 100 requests-per-second (RPS) limit, you need a precise way to shape your traffic.&lt;/p&gt;

&lt;p&gt;Enter the &lt;strong&gt;Token Bucket Rate Limiter&lt;/strong&gt; in &lt;a href="https://github.com/cinar/resile" rel="noopener noreferrer"&gt;Resile&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem: Unbounded Traffic
&lt;/h2&gt;

&lt;p&gt;In a distributed environment, your clients don't know about each other. If 50 different microservice instances all decide to call a downstream API at the same time, the aggregate traffic can easily exceed the capacity of the target system. &lt;/p&gt;

&lt;p&gt;When you exceed these limits, you'll often see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;HTTP 429 (Too Many Requests)&lt;/strong&gt;: Downstream services start rejecting you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cascading Latency&lt;/strong&gt;: The target system slows down for &lt;em&gt;everyone&lt;/em&gt; because it's processing too many requests at once.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost Overruns&lt;/strong&gt;: Many cloud providers and SaaS APIs charge significant premiums for exceeding agreed-upon quotas.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Solution: The Token Bucket Algorithm
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;Token Bucket&lt;/strong&gt; is a classic algorithm used for traffic shaping. &lt;/p&gt;

&lt;p&gt;Imagine a bucket that refills with "tokens" at a constant rate (e.g., 100 tokens per second). Every request must consume a token from the bucket. If the bucket is empty, the request is rejected immediately. This allows for small "bursts" (filling the bucket) while maintaining a precise long-term average rate.&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementing with Resile:
&lt;/h3&gt;

&lt;p&gt;Resile makes adding rate limiting to your executions simple.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Allow 100 requests per second.&lt;/span&gt;
&lt;span class="c"&gt;// If the limit is exceeded, it fails fast with resile.ErrRateLimitExceeded.&lt;/span&gt;
&lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;resile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DoErr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;resile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithRateLimiter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Rate Limiting vs. Adaptive Retries
&lt;/h3&gt;

&lt;p&gt;Wait, doesn't Resile already have &lt;code&gt;AdaptiveBucket&lt;/code&gt;? What's the difference?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AdaptiveBucket&lt;/strong&gt; is &lt;em&gt;success-based&lt;/em&gt;. It tracks how many requests are succeeding vs. failing and throttles &lt;em&gt;retries&lt;/em&gt; accordingly. It's designed specifically to prevent "retry storms" when a service is failing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RateLimiter&lt;/strong&gt; is &lt;em&gt;time-based&lt;/em&gt;. It enforces a strict, constant quota of requests over a time interval. It’s designed for general traffic shaping and quota management.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For maximum protection, you can even use them together!&lt;/p&gt;




&lt;h2&gt;
  
  
  Shared Rate Limiters
&lt;/h2&gt;

&lt;p&gt;Often, you want to enforce a global rate limit across your entire service instance. You can create a shared &lt;code&gt;RateLimiter&lt;/code&gt; and pass it to multiple executions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Shared rate limiter for a specific API key or downstream service&lt;/span&gt;
&lt;span class="n"&gt;limiter&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;resile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewRateLimiter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// Each call will consume tokens from the same shared bucket.&lt;/span&gt;
&lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;resile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DoErr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;myAction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;resile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithRateLimiterInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;limiter&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Observability: Seeing the Shaping
&lt;/h2&gt;

&lt;p&gt;Knowing &lt;em&gt;when&lt;/em&gt; and &lt;em&gt;why&lt;/em&gt; your traffic is being throttled is essential for operational visibility. &lt;/p&gt;

&lt;p&gt;If you use Resile's telemetry integrations (like &lt;code&gt;slog&lt;/code&gt; or &lt;code&gt;OpenTelemetry&lt;/code&gt;), you'll get automatic visibility into these events. The &lt;code&gt;OnRateLimitExceeded&lt;/code&gt; event is triggered whenever a request is rejected by the rate limiter, allowing you to monitor your quota utilization in real-time.&lt;/p&gt;




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

&lt;p&gt;Rate limiting is not just about saying "no"; it's about being a good citizen in a distributed ecosystem. By respecting boundaries and shaping your traffic at the source, you protect both your own service and the systems you depend on.&lt;/p&gt;

&lt;p&gt;Resile provides a production-grade rate limiter that integrates seamlessly into your resilience policies, giving you fine-grained control over your traffic flow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Learn more about Resile:&lt;/strong&gt; &lt;a href="https://github.com/cinar/resile" rel="noopener noreferrer"&gt;github.com/cinar/resile&lt;/a&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>microservices</category>
      <category>sre</category>
      <category>devops</category>
    </item>
    <item>
      <title>Stop the Domino Effect: Bulkhead Isolation in Go</title>
      <dc:creator>Onur Cinar</dc:creator>
      <pubDate>Sun, 22 Mar 2026 17:42:19 +0000</pubDate>
      <link>https://dev.to/onurcinar/stop-the-domino-effect-bulkhead-isolation-in-go-5cgl</link>
      <guid>https://dev.to/onurcinar/stop-the-domino-effect-bulkhead-isolation-in-go-5cgl</guid>
      <description>&lt;p&gt;In a distributed system, failure is inevitable. But a failure in one part of your system shouldn't bring down everything else. &lt;/p&gt;

&lt;p&gt;Imagine your Go service depends on three different downstream APIs: Payments, Inventory, and Recommendations. Suddenly, the Recommendations API starts taking 30 seconds to respond. If your service doesn't have isolation, your goroutines will start piling up waiting for Recommendations. Eventually, you'll hit your process limit, and even the critical Payments API calls will start failing because there are no resources left to handle them.&lt;/p&gt;

&lt;p&gt;This is the &lt;strong&gt;Domino Effect&lt;/strong&gt;, and the &lt;strong&gt;Bulkhead Pattern&lt;/strong&gt; is how you stop it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem: Resource Exhaustion
&lt;/h2&gt;

&lt;p&gt;When one dependency slows down, it consumes resources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Goroutines&lt;/strong&gt;: Blocked waiting for a response.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory&lt;/strong&gt;: Each blocked goroutine carries a stack.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;File Descriptors/Sockets&lt;/strong&gt;: Open connections to the slow service.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without a bulkhead, a single slow dependency can "starve" the rest of your application, leading to a total system collapse.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Solution: The Bulkhead Pattern
&lt;/h2&gt;

&lt;p&gt;Named after the partitioned sections of a ship's hull, a &lt;strong&gt;Bulkhead&lt;/strong&gt; isolates failures. If one section of the ship is flooded, the others remain buoyant. In software, we achieve this by limiting the number of concurrent executions allowed for a specific resource or dependency.&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementing with Resile:
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/cinar/resile" rel="noopener noreferrer"&gt;Resile&lt;/a&gt; makes it trivial to add bulkhead isolation to any operation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Allow only 10 concurrent calls to this specific operation.&lt;/span&gt;
&lt;span class="c"&gt;// If an 11th call comes in, it fails fast with resile.ErrBulkheadFull.&lt;/span&gt;
&lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;resile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DoErr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;resile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithBulkhead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Using a Shared Bulkhead
&lt;/h3&gt;

&lt;p&gt;Often, you want to limit concurrency across multiple different call sites that hit the same downstream service. You can create a shared &lt;code&gt;Bulkhead&lt;/code&gt; instance for this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Create a shared bulkhead for the "Inventory Service"&lt;/span&gt;
&lt;span class="n"&gt;inventoryBulkhead&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;resile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewBulkhead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// Call Site A&lt;/span&gt;
&lt;span class="n"&gt;resile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DoErr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fetchItem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithBulkheadInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inventoryBulkhead&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c"&gt;// Call Site B&lt;/span&gt;
&lt;span class="n"&gt;resile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DoErr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;updateStock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithBulkheadInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inventoryBulkhead&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By sharing the instance, you ensure that the &lt;em&gt;total&lt;/em&gt; concurrency hitting the Inventory Service never exceeds 20, regardless of which part of your code is making the call.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why "Fail-Fast" Matters
&lt;/h2&gt;

&lt;p&gt;When a bulkhead is full, Resile immediately returns &lt;code&gt;resile.ErrBulkheadFull&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;This is much better than waiting for a timeout. By failing fast, you:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Preserve Resources&lt;/strong&gt;: You don't spawn another goroutine or open another connection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Provide Immediate Feedback&lt;/strong&gt;: Your upstream callers get an error instantly and can decide how to handle it (e.g., show a cached result or a "service busy" message).&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Observability: Monitoring the Walls
&lt;/h2&gt;

&lt;p&gt;You need to know when your bulkheads are working. If a bulkhead is frequently full, it might mean your downstream service is struggling, or you need to re-evaluate your capacity limits.&lt;/p&gt;

&lt;p&gt;If you use Resile's telemetry integrations (like &lt;code&gt;slog&lt;/code&gt; or &lt;code&gt;OpenTelemetry&lt;/code&gt;), you'll get automatic alerts when a bulkhead saturates. The &lt;code&gt;OnBulkheadFull&lt;/code&gt; event is triggered every time a request is rejected due to capacity limits.&lt;/p&gt;




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

&lt;p&gt;Bulkheads are a fundamental building block of resilient systems. By isolating your dependencies, you ensure that a local fire doesn't become a global conflagration.&lt;/p&gt;

&lt;p&gt;Resile provides a clean, "Go-native" way to implement bulkheads without complex boilerplate, allowing you to focus on your business logic while keeping your system stable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Explore Resile on GitHub:&lt;/strong&gt; &lt;a href="https://github.com/cinar/resile" rel="noopener noreferrer"&gt;github.com/cinar/resile&lt;/a&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>microservices</category>
      <category>backend</category>
      <category>distributedsystems</category>
    </item>
  </channel>
</rss>
