<?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: Bobby Larson</title>
    <description>The latest articles on DEV Community by Bobby Larson (@karma0).</description>
    <link>https://dev.to/karma0</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%2F1026012%2F37455780-ca63-4c8f-94c1-555454c96d00.jpeg</url>
      <title>DEV Community: Bobby Larson</title>
      <link>https://dev.to/karma0</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/karma0"/>
    <language>en</language>
    <item>
      <title>Testing the claim: a degraded-link matrix as a required CI gate</title>
      <dc:creator>Bobby Larson</dc:creator>
      <pubDate>Wed, 19 Aug 2026 21:04:51 +0000</pubDate>
      <link>https://dev.to/karma0/testing-the-claim-a-degraded-link-matrix-as-a-required-ci-gate-4edm</link>
      <guid>https://dev.to/karma0/testing-the-claim-a-degraded-link-matrix-as-a-required-ci-gate-4edm</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a writeup of building a required CI gate for degraded-network behavior. The system under test is a robotics fleet substrate, but the finding applies to anyone shaping networks in CI.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Ganglion exists to reach robots on networks nobody controls. Warehouse Wi-Fi, carrier CGNAT, a hospital VLAN, a customer firewall that was configured once in 2019 and has not been touched since.&lt;/p&gt;

&lt;p&gt;Until this week that claim was a sentence on a website. CI ran on clean loopback, everything was green, and the failure modes that actually matter in the field were the exact ones the test suite could never produce.&lt;/p&gt;

&lt;p&gt;That is now a required gate. Every push to main runs the full deploy, invoke and verify round trip over the relay against five shaped network profiles, and all five have to pass before anything merges.&lt;/p&gt;

&lt;p&gt;I build Ganglion, so treat the enthusiasm accordingly. The part worth your time is not that it went green. It is what I got wrong on the way there.&lt;/p&gt;

&lt;h2&gt;
  
  
  The five profiles
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;clean&lt;/strong&gt;: baseline, no shaping. If this one fails, something else is broken.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;lossy&lt;/strong&gt;: packet loss with light reordering.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;high-latency&lt;/strong&gt;: 250ms round trip.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;asymmetric&lt;/strong&gt;: plentiful downlink, starved uplink. This is the one nobody tests and the one teleop actually dies on, because control acknowledgements go the starved direction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;nat-relay&lt;/strong&gt;: endpoints with no route to each other at all, forcing hole punching to fail and relay fallback to carry the session.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The last two are the ones I care about. Loss and latency are what people imagine a bad network is. Asymmetry and no-direct-route are what a bad network usually is.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I got wrong
&lt;/h2&gt;

&lt;p&gt;The original design assumed you can pin netem's seed and get a repeatable lossy run. Two profiles: a pinned-seed one that gates the build, and a nastier randomized one that runs nightly and is allowed to fail.&lt;/p&gt;

&lt;p&gt;You cannot pin netem's seed. Its loss and jitter draw from the kernel RNG and there is no seed parameter to set. A "deterministic lossy netem profile" is not a thing that exists.&lt;/p&gt;

&lt;p&gt;This matters more than a wrong detail usually would, because the whole design rested on it. A gate that fails randomly is worse than no gate at all. It does not catch regressions, it teaches everyone to re-run the job until it passes, and after a month nobody reads red as meaning anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually reproduces
&lt;/h2&gt;

&lt;p&gt;The fix was to build the gate out of only the mechanisms that reproduce exactly, and to be honest that this is a smaller set than netem advertises:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fixed netem delay.&lt;/strong&gt; Constant delay, zero jitter. Deterministic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;tbf rate caps.&lt;/strong&gt; Bandwidth ceilings are a token bucket, not a distribution. Deterministic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;iptables -m statistic --mode nth&lt;/code&gt;&lt;/strong&gt; for loss. This drops precisely every Nth packet rather than N percent on average. Every 33rd packet is roughly three percent loss, and critically it is the &lt;em&gt;same&lt;/em&gt; three percent every run.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Route blocking&lt;/strong&gt; for the nat-relay case. Either a route exists or it does not.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Randomized netem still runs. It just runs nightly, in a separate non-blocking job, and a failure opens an issue instead of stopping a merge.&lt;/p&gt;

&lt;p&gt;The nightly job generates its parameters deterministically from a recorded seed, so a failing run can be replayed. Worth being precise about what that buys, because it is easy to oversell: replaying reproduces the impairment &lt;em&gt;distribution&lt;/em&gt;, not the packet-level draw. You get the same shape of bad network, not the same individual dropped packets. That is documented in the README rather than glossed over, and it is exactly why chaos never blocks a merge.&lt;/p&gt;

&lt;h2&gt;
  
  
  Riding the harness that already existed
&lt;/h2&gt;

&lt;p&gt;The obvious implementation is a fresh veth pair or a pair of network namespaces built specifically for shaping. I did not do that, and I think the reason generalizes.&lt;/p&gt;

&lt;p&gt;There was already an end-to-end dispatch harness running the real deploy, invoke and verify round trip through the relay. Building a second rig would have given me a network test that did not exercise the actual product path, which is the failure mode where CI is green and the thing still breaks.&lt;/p&gt;

&lt;p&gt;So the matrix rides the existing harness. Shaping is applied inside the robot and operator containers before the agent starts. netem inside Docker was already proven green in CI by an existing mobile-CGNAT scenario, so the risky part was already de-risked. One rig, one new axis.&lt;/p&gt;

&lt;h2&gt;
  
  
  Record what you shaped
&lt;/h2&gt;

&lt;p&gt;Every run writes a JSON artifact: mode, seed, the exact shaping commands issued, duration, and result.&lt;/p&gt;

&lt;p&gt;This came out of a conversation on ROS Discourse where someone building replay tooling for robot fleets made the point better than I would have. Injected faults that are not recorded produce failures you can see and cannot get back. The fault has to be part of the run's record, or a genuine bug becomes a flaky ghost and gets closed as unreproducible.&lt;/p&gt;

&lt;p&gt;Shape at the qdisc, record what you shaped. The two compose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping CI cheap
&lt;/h2&gt;

&lt;p&gt;A five-profile matrix is an easy way to quadruple your CI bill. What kept it small:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One required-gate job, not five parallel ones. The profiles run sequentially inside it.&lt;/li&gt;
&lt;li&gt;One build, reused across all five. Compiling five times to test networking is pure waste.&lt;/li&gt;
&lt;li&gt;Main-push only. Not every push to every branch.&lt;/li&gt;
&lt;li&gt;Early exit on docs-only pushes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That lands around 15 to 25 minutes per push. The nightly chaos run is scheduled separately and blocks nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The de-flaking pass, which is the part everyone skips
&lt;/h2&gt;

&lt;p&gt;First full run was not clean, and the failure was not in the shaping. The nat-relay profile had a DNS race: containers with no route between them were resolving each other inconsistently at startup, and the test failed in a way that looked like a shaping bug. Fixed with static compose IPs.&lt;/p&gt;

&lt;p&gt;The gate profiles also retry once and attach compose logs on failure. That is a deliberate concession. A gate that is right in principle and flaky in practice gets disabled within two weeks, and then you have neither.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it landed
&lt;/h2&gt;

&lt;p&gt;Five of five deterministic profiles passing on main. Clean, lossy, high-latency, asymmetric, nat-relay.&lt;/p&gt;

&lt;p&gt;The honest framing is not that Ganglion now works on bad networks. It is that a specific set of bad-network behaviors is now regression-tested, and if I break relay fallback under an asymmetric link, the build tells me instead of a customer telling me.&lt;/p&gt;

&lt;p&gt;That is a smaller claim than the marketing sentence. It is also the first version of it I can point at.&lt;/p&gt;




&lt;p&gt;Ganglion is Apache-2.0. The harness lives in &lt;code&gt;test-harness/degraded-link/&lt;/code&gt;, including the determinism contract and the replay command: &lt;a href="https://github.com/RobotDen/ganglion" rel="noopener noreferrer"&gt;https://github.com/RobotDen/ganglion&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://automaton.run/post/degraded-link-ci-gate" rel="noopener noreferrer"&gt;automaton.run&lt;/a&gt;. Ganglion is built for ROS 2 robot fleets, but the substrate underneath is transport-agnostic: outbound-only reachability, signed WASM tooling, and default-deny policy work the same for any distributed system on networks you don't control.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ci</category>
      <category>rust</category>
      <category>ros2</category>
      <category>networking</category>
    </item>
    <item>
      <title>Tafy, A Meal Planner and Recipe Generator for Busy Devs!</title>
      <dc:creator>Bobby Larson</dc:creator>
      <pubDate>Mon, 15 Apr 2024 06:23:11 +0000</pubDate>
      <link>https://dev.to/karma0/tafy-a-meal-planner-and-recipe-generator-for-busy-devs-2n1d</link>
      <guid>https://dev.to/karma0/tafy-a-meal-planner-and-recipe-generator-for-busy-devs-2n1d</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/devteam/join-us-for-the-cloudflare-ai-challenge-3000-in-prizes-5f99"&gt;Cloudflare AI Challenge&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Tafy is an intuitive meal planner that takes all of the pain out of planning your meals for the week by generating recipes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;You can demo the application here --&amp;gt; &lt;a href="https://tafy.recipes" rel="noopener noreferrer"&gt;https://tafy.recipes&lt;/a&gt;&lt;/p&gt;

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

&lt;h2&gt;
  
  
  My Code
&lt;/h2&gt;

&lt;p&gt;You can peruse at your leisure here: &lt;a href="https://github.com/tafy-io/tafy-demo" rel="noopener noreferrer"&gt;tafy-demo&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Journey
&lt;/h2&gt;

&lt;p&gt;This project has been something that I've been stewing on for years (pun intended)!  I decided to pursue it as a business, then shortly after ChatGPT came out and I thought I was done for ... but then I realized that it would just make it that much better!&lt;/p&gt;

&lt;p&gt;I decided to do this as soon as I heard about the challenge (about 5 days ago!) so I got to work, and decided to beg my family for forgiveness later - sorry, honey!&lt;/p&gt;

&lt;p&gt;In my work, I've been doing freelance with LLMs and generative AI and so I've been following the space heavily, and learned recently about &lt;code&gt;CopilotKit&lt;/code&gt; in an article on dev.to.  So I had to try it!&lt;/p&gt;

&lt;p&gt;With that, I had to hack it a bit to get the tooling to work with Cloudflare since a lot of text generation models don't support calling tools.  Luckily, &lt;code&gt;@hf/nousresearch/hermes-2-pro-mistral-7b&lt;/code&gt; hosted in Cloudflare Workers AI was able to rescue me, but the model did break my search integration.&lt;/p&gt;

&lt;p&gt;I also decided to go with the &lt;code&gt;@cf/bytedance/stable-diffusion-xl&lt;/code&gt; model for generating images, and so far I've been really impressed!&lt;/p&gt;

&lt;p&gt;Finally, I'm using OpenAI's text-to-speech model for verbal descriptions of the meal plans. Though I was hoping to turn this into a means of getting a recipe read off to you while you make it, I actually like the smooth-jazz DJ AI voice-over while I'm looking at the sweet pics of the food that it comes up with!&lt;/p&gt;

&lt;p&gt;I'm proud of what I've been able to accomplish what I did in such a short time, but I couldn't have done it without the help of folks in chat - thank you!!!&lt;/p&gt;

&lt;p&gt;Check out my README on GitHub for a list of things that I'll be working on next, and maybe someday, if everything works out, I'll make (this website)[&lt;a href="https://tafy.io" rel="noopener noreferrer"&gt;https://tafy.io&lt;/a&gt;] live!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multiple Models and/or Triple Task Types&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I used multiple models and tasks:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Models&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;@hf/nousresearch/hermes-2-pro-mistral-7b&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@cf/bytedance/stable-diffusion-xl&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;OpenAI text-to-speech&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Tasks&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I'm not sure what constitutes as a task, so I'll take a shot here...&lt;/p&gt;

&lt;p&gt;[x] The LLM calls tools which equate to "research," each a text generation task in its own right: search, curate, write, critique, revise.&lt;br&gt;
[x] The recipe's summary is sent to OpenAI's text-to-speech API.&lt;br&gt;
[x] Cloudflare Worker - text to image conversion.&lt;br&gt;
[x] Web search (partial, this works with GPT-4, but not Hermes because I can't get it to output JSON).&lt;/p&gt;

&lt;p&gt;Things I didn't get to, but may be partially setup:&lt;/p&gt;

&lt;p&gt;[] Vectorize store with recipes deposited and used to supplement.&lt;br&gt;
[] Preferences stored on disk, in a DB, or in object storage.&lt;/p&gt;

</description>
      <category>cloudflarechallenge</category>
      <category>devchallenge</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
