<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>David Bensoussan</title><link>https://attheedge.dev/</link><description>Recent content on David Bensoussan</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Tue, 08 Sep 2026 10:00:00 +0200</lastBuildDate><atom:link href="https://attheedge.dev/index.xml" rel="self" type="application/rss+xml"/><item><title>Separating OS and Application Updates</title><link>https://attheedge.dev/post/2026-09-08-separating-os-and-application-updates/</link><pubDate>Tue, 08 Sep 2026 10:00:00 +0200</pubDate><guid>https://attheedge.dev/post/2026-09-08-separating-os-and-application-updates/</guid><description>&lt;p&gt;A process restart takes a few seconds and only loses its own memory. A reboot takes the whole edge server down: the tunnel to the hub, the local Kubernetes node, everything the robot depends on it for. Build one pipeline for both and size it for the reboot, and every small app fix now waits on the same caution a reboot needs, for no reason.&lt;/p&gt;
&lt;p&gt;This builds on &lt;a href="https://attheedge.dev/post/2026-08-11-updating-robots-in-the-field/"&gt;updating robots in the field&lt;/a&gt; and &lt;a href="https://attheedge.dev/post/2026-08-25-a-reference-architecture-for-robot-fleets/"&gt;the reference architecture&lt;/a&gt;. Read those first if you haven&amp;rsquo;t.&lt;/p&gt;</description></item><item><title>Platform Adoption: Tooling, Training, Buy-In</title><link>https://attheedge.dev/post/2026-09-01-platform-adoption-tooling-training-buy-in/</link><pubDate>Tue, 01 Sep 2026 10:00:00 +0200</pubDate><guid>https://attheedge.dev/post/2026-09-01-platform-adoption-tooling-training-buy-in/</guid><description>&lt;p&gt;Article three covered the architecture: the hub, the edge servers, the GitOps pipeline, the signed OS images. Building that and getting people to use it are two different jobs.&lt;/p&gt;
&lt;p&gt;This builds on &lt;a href="https://attheedge.dev/post/2026-08-18-what-a-fleet-platform-costs/"&gt;what a fleet platform costs&lt;/a&gt; and &lt;a href="https://attheedge.dev/post/2026-08-25-a-reference-architecture-for-robot-fleets/"&gt;the reference architecture&lt;/a&gt;. Read those first if you haven&amp;rsquo;t.&lt;/p&gt;
&lt;p&gt;Nothing changes on its own once the platform is live. Engineers keep SSHing into a site and pulling code by hand, even after you tell them the old way is over, because that&amp;rsquo;s the workflow they know and trust. Getting them onto the new one takes a transition period, documentation, more than one training session, and one-on-one conversations to answer questions. Only after all that does SSH access get turned off.&lt;/p&gt;</description></item><item><title>A Reference Architecture for Robot Fleets</title><link>https://attheedge.dev/post/2026-08-25-a-reference-architecture-for-robot-fleets/</link><pubDate>Tue, 25 Aug 2026 10:00:00 +0200</pubDate><guid>https://attheedge.dev/post/2026-08-25-a-reference-architecture-for-robot-fleets/</guid><description>&lt;p&gt;The last article listed five capabilities and the order to build them in. Before building anything, we must agree on the architecture: what runs where, and which direction each connection travels.&lt;/p&gt;
&lt;p&gt;The architecture has three layers. The robot runs its own ROS2 stack as before. Above it, one or more edge servers per site carry updates and data between the robot and the hub. Above the edge servers is a single hub, shared by the whole fleet, and every edge server connects to it. The hub never opens a connection to a site, that&amp;rsquo;s what makes control and security possible.&lt;/p&gt;</description></item><item><title>What a Fleet Platform Costs</title><link>https://attheedge.dev/post/2026-08-18-what-a-fleet-platform-costs/</link><pubDate>Tue, 18 Aug 2026 01:00:00 +0200</pubDate><guid>https://attheedge.dev/post/2026-08-18-what-a-fleet-platform-costs/</guid><description>&lt;p&gt;The last article said you need a DevOps engineer, in-house or freelance. Before you take that to your CEO, you need to know what it costs.&lt;/p&gt;
&lt;p&gt;You must be able to tell him how long he should pay this person before seeing any result. In this article, we will dive into this.&lt;/p&gt;
&lt;p&gt;We previously listed five topics a fleet platform gives you: speed, control, visibility, security and compliance.&lt;/p&gt;
&lt;figure class="diagram"&gt;
 &lt;div class="diagram-frame"&gt;
 &lt;div class="diagram-scroll"&gt;
&lt;svg viewBox="0 0 1500 1500" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Six panels of fleet platform maturity in a three-by-two grid, each drawn from the same three-robot fleet with one capability added per panel. Before: three robots with no laptop and no links, each drawn with a different dash pattern, drifted apart. Speed, one month: a laptop appears as the build source and every robot becomes identical, linked to it. Control, three to six months: marks travel outward along each link, with one reversed mark for rolling a change back. Visibility, three months: one robot turns warn-coloured and a warn-coloured mark travels back down its link to the laptop, whose screen shows a warning triangle on arrival. Security, six months: a shell encloses the whole fleet. Compliance, three months: a record appears above the fleet, one row per robot with a leader line down to its own robot, columns for points in time, and the current column marked with a diamond." style="width:100%;height:auto;display:block"&gt;
&lt;defs&gt;
&lt;g id="bot-owned" fill="none" stroke="var(--diagram-owned)" stroke-linecap="round" stroke-linejoin="round"&gt;
&lt;path d="M15 -22 L31 -45" stroke-width="3"&gt;&lt;/path&gt;
&lt;path d="M31 -45 L53 -36.5" stroke-width="3"&gt;&lt;/path&gt;
&lt;circle cx="31" cy="-45" r="5" fill="var(--diagram-external)" stroke-width="2.5"&gt;&lt;/circle&gt;
&lt;circle cx="58" cy="-35" r="4.5" fill="var(--diagram-external)" stroke-width="2.5"&gt;&lt;/circle&gt;
&lt;rect x="-30" y="-22" width="60" height="44" rx="10" fill="var(--diagram-external)" stroke-width="3"&gt;&lt;/rect&gt;
&lt;/g&gt;
&lt;g id="bot-warn" fill="none" stroke="var(--diagram-warn)" stroke-linecap="round" stroke-linejoin="round"&gt;
&lt;path d="M15 -22 L31 -45" stroke-width="3"&gt;&lt;/path&gt;
&lt;path d="M31 -45 L53 -36.5" stroke-width="3"&gt;&lt;/path&gt;
&lt;circle cx="31" cy="-45" r="5" fill="var(--diagram-external)" stroke-width="2.5"&gt;&lt;/circle&gt;
&lt;circle cx="58" cy="-35" r="4.5" fill="var(--diagram-external)" stroke-width="2.5"&gt;&lt;/circle&gt;
&lt;rect x="-30" y="-22" width="60" height="44" rx="10" fill="var(--diagram-external)" stroke-width="3"&gt;&lt;/rect&gt;
&lt;/g&gt;
&lt;g id="bot-ext-a" fill="none" stroke="var(--diagram-external-line)" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="7 5"&gt;
&lt;path d="M15 -22 L31 -45" stroke-width="3"&gt;&lt;/path&gt;
&lt;path d="M31 -45 L53 -36.5" stroke-width="3"&gt;&lt;/path&gt;
&lt;circle cx="31" cy="-45" r="5" fill="var(--diagram-external)" stroke-width="2.5"&gt;&lt;/circle&gt;
&lt;circle cx="58" cy="-35" r="4.5" fill="var(--diagram-external)" stroke-width="2.5"&gt;&lt;/circle&gt;
&lt;rect x="-30" y="-22" width="60" height="44" rx="10" fill="var(--diagram-external)" stroke-width="3"&gt;&lt;/rect&gt;
&lt;/g&gt;
&lt;g id="bot-ext-b" fill="none" stroke="var(--diagram-external-line)" stroke-linecap="round" stroke-linejoin="round"&gt;
&lt;path d="M15 -22 L31 -45" stroke-width="3"&gt;&lt;/path&gt;
&lt;path d="M31 -45 L53 -36.5" stroke-width="3"&gt;&lt;/path&gt;
&lt;circle cx="31" cy="-45" r="5" fill="var(--diagram-external)" stroke-width="2.5"&gt;&lt;/circle&gt;
&lt;circle cx="58" cy="-35" r="4.5" fill="var(--diagram-external)" stroke-width="2.5"&gt;&lt;/circle&gt;
&lt;rect x="-30" y="-22" width="60" height="44" rx="10" fill="var(--diagram-external)" stroke-width="3"&gt;&lt;/rect&gt;
&lt;/g&gt;
&lt;g id="bot-ext-c" fill="none" stroke="var(--diagram-external-line)" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="2 6 8 4"&gt;
&lt;path d="M15 -22 L31 -45" stroke-width="3"&gt;&lt;/path&gt;
&lt;path d="M31 -45 L53 -36.5" stroke-width="3"&gt;&lt;/path&gt;
&lt;circle cx="31" cy="-45" r="5" fill="var(--diagram-external)" stroke-width="2.5"&gt;&lt;/circle&gt;
&lt;circle cx="58" cy="-35" r="4.5" fill="var(--diagram-external)" stroke-width="2.5"&gt;&lt;/circle&gt;
&lt;rect x="-30" y="-22" width="60" height="44" rx="10" fill="var(--diagram-external)" stroke-width="3"&gt;&lt;/rect&gt;
&lt;/g&gt;
&lt;g id="laptop-ok" fill="none" stroke="var(--diagram-ok)" stroke-linecap="round" stroke-linejoin="round"&gt;
&lt;rect x="-22" y="-34" width="44" height="28" rx="3" fill="var(--diagram-external)" stroke-width="3"&gt;&lt;/rect&gt;
&lt;path d="M -22 -6 L 22 -6 L 30 8 L -30 8 Z" fill="var(--diagram-external)" stroke-width="3"&gt;&lt;/path&gt;
&lt;/g&gt;
&lt;rect id="shell-ok" x="-46" y="-38" width="92" height="76" rx="20" fill="none" stroke="var(--diagram-ok)" stroke-width="4"&gt;&lt;/rect&gt;
&lt;g id="laptop-alert" fill="none" stroke="var(--diagram-ok)" stroke-linecap="round" stroke-linejoin="round"&gt;
&lt;rect x="-22" y="-34" width="44" height="28" rx="3" fill="var(--diagram-external)" stroke-width="3"&gt;&lt;/rect&gt;
&lt;path d="M -22 -6 L 22 -6 L 30 8 L -30 8 Z" fill="var(--diagram-external)" stroke-width="3"&gt;&lt;/path&gt;
&lt;path d="M 0 -29 L 9.5 -13 L -9.5 -13 Z" fill="var(--diagram-warn)" stroke="none"&gt;&lt;/path&gt;
&lt;line x1="0" y1="-25" x2="0" y2="-18" stroke="var(--diagram-external)" stroke-width="2.5" stroke-linecap="round"&gt;&lt;/line&gt;
&lt;circle cx="0" cy="-15" r="1.4" fill="var(--diagram-external)" stroke="none"&gt;&lt;/circle&gt;
&lt;/g&gt;
&lt;marker id="arrow-owned" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse"&gt;
&lt;path d="M0,0 L10,5 L0,10 Z" fill="var(--diagram-owned)"&gt;&lt;/path&gt;
&lt;/marker&gt;
&lt;marker id="arrow-warn" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse"&gt;
&lt;path d="M0,0 L10,5 L0,10 Z" fill="var(--diagram-warn)"&gt;&lt;/path&gt;
&lt;/marker&gt;
&lt;/defs&gt;

&lt;g stroke="var(--muted)" stroke-width="1.5" stroke-dasharray="2 8" fill="none"&gt;
&lt;line x1="500" y1="10" x2="500" y2="725"&gt;&lt;/line&gt;
&lt;line x1="1000" y1="10" x2="1000" y2="725"&gt;&lt;/line&gt;
&lt;line x1="500" y1="775" x2="500" y2="1490"&gt;&lt;/line&gt;
&lt;line x1="1000" y1="775" x2="1000" y2="1490"&gt;&lt;/line&gt;
&lt;line x1="0" y1="750" x2="1500" y2="750"&gt;&lt;/line&gt;
&lt;/g&gt;

&lt;text x="250" y="46" text-anchor="middle" font-family="var(--serif)" font-size="36" font-weight="600" fill="var(--text)"&gt;Before&lt;/text&gt;
&lt;g transform="translate(250 110)"&gt;
&lt;use href="#bot-ext-a" transform="translate(-130 320) scale(2)"&gt;&lt;/use&gt;
&lt;use href="#bot-ext-b" transform="translate(0 320) scale(2)"&gt;&lt;/use&gt;
&lt;use href="#bot-ext-c" transform="translate(130 320) scale(2)"&gt;&lt;/use&gt;
&lt;/g&gt;

&lt;text x="750" y="46" text-anchor="middle" font-family="var(--serif)" font-size="36" font-weight="600" fill="var(--text)"&gt;Speed&lt;/text&gt;
&lt;circle cx="750" cy="86" r="11" fill="var(--diagram-owned)"&gt;&lt;/circle&gt;
&lt;text x="750" y="128" text-anchor="middle" font-family="var(--serif)" font-size="34" fill="var(--muted)"&gt;1 month&lt;/text&gt;
&lt;g transform="translate(750 110)"&gt;
&lt;g stroke="var(--diagram-owned)" stroke-width="2.5"&gt;
&lt;line x1="0" y1="560" x2="-130" y2="320"&gt;&lt;/line&gt;
&lt;line x1="0" y1="560" x2="0" y2="320"&gt;&lt;/line&gt;
&lt;line x1="0" y1="560" x2="130" y2="320"&gt;&lt;/line&gt;
&lt;/g&gt;
&lt;use href="#bot-owned" transform="translate(-130 320) scale(2)"&gt;&lt;/use&gt;
&lt;use href="#bot-owned" transform="translate(0 320) scale(2)"&gt;&lt;/use&gt;
&lt;use href="#bot-owned" transform="translate(130 320) scale(2)"&gt;&lt;/use&gt;
&lt;use href="#laptop-ok" transform="translate(0 560) scale(2.1)"&gt;&lt;/use&gt;
&lt;/g&gt;

&lt;text x="1230" y="46" text-anchor="middle" font-family="var(--serif)" font-size="36" font-weight="600" fill="var(--text)"&gt;Control&lt;/text&gt;
&lt;g&gt;
&lt;circle cx="1155" cy="86" r="11" fill="var(--diagram-owned)"&gt;&lt;/circle&gt;
&lt;circle cx="1185" cy="86" r="11" fill="var(--diagram-owned)"&gt;&lt;/circle&gt;
&lt;circle cx="1215" cy="86" r="11" fill="var(--diagram-owned)"&gt;&lt;/circle&gt;
&lt;circle cx="1245" cy="86" r="11" fill="var(--diagram-external)" stroke="var(--diagram-owned)" stroke-width="2.5"&gt;&lt;/circle&gt;
&lt;circle cx="1275" cy="86" r="11" fill="var(--diagram-external)" stroke="var(--diagram-owned)" stroke-width="2.5"&gt;&lt;/circle&gt;
&lt;circle cx="1305" cy="86" r="11" fill="var(--diagram-external)" stroke="var(--diagram-owned)" stroke-width="2.5"&gt;&lt;/circle&gt;
&lt;/g&gt;
&lt;text x="1230" y="128" text-anchor="middle" font-family="var(--serif)" font-size="34" fill="var(--muted)"&gt;3 to 6 months&lt;/text&gt;
&lt;g transform="translate(1230 110)"&gt;
&lt;g stroke="var(--diagram-owned)" stroke-width="2.5"&gt;
&lt;line x1="0" y1="560" x2="-130" y2="320"&gt;&lt;/line&gt;
&lt;line x1="0" y1="560" x2="0" y2="320"&gt;&lt;/line&gt;
&lt;line x1="0" y1="560" x2="130" y2="320"&gt;&lt;/line&gt;
&lt;/g&gt;
&lt;g stroke="var(--diagram-owned)" stroke-width="5" stroke-linecap="round" opacity="0.5"&gt;
&lt;line x1="-113.8" y1="350" x2="-84" y2="405" marker-end="url(#arrow-owned)"&gt;&lt;/line&gt;
&lt;line x1="0" y1="350" x2="0" y2="405" marker-end="url(#arrow-owned)"&gt;&lt;/line&gt;
&lt;line x1="113.8" y1="350" x2="84" y2="405" marker-end="url(#arrow-owned)"&gt;&lt;/line&gt;
&lt;line x1="-54.2" y1="460" x2="-75.8" y2="420" marker-end="url(#arrow-owned)"&gt;&lt;/line&gt;
&lt;line x1="0" y1="420" x2="0" y2="460" marker-end="url(#arrow-owned)"&gt;&lt;/line&gt;
&lt;line x1="54.2" y1="460" x2="75.8" y2="420" marker-end="url(#arrow-owned)"&gt;&lt;/line&gt;
&lt;/g&gt;
&lt;use href="#bot-owned" transform="translate(-130 320) scale(2)"&gt;&lt;/use&gt;
&lt;use href="#bot-owned" transform="translate(0 320) scale(2)"&gt;&lt;/use&gt;
&lt;use href="#bot-owned" transform="translate(130 320) scale(2)"&gt;&lt;/use&gt;
&lt;use href="#laptop-ok" transform="translate(0 560) scale(2.1)"&gt;&lt;/use&gt;
&lt;/g&gt;

&lt;text x="250" y="811" text-anchor="middle" font-family="var(--serif)" font-size="36" font-weight="600" fill="var(--text)"&gt;Visibility&lt;/text&gt;
&lt;g fill="var(--diagram-owned)"&gt;
&lt;circle cx="220" cy="851" r="11"&gt;&lt;/circle&gt;
&lt;circle cx="250" cy="851" r="11"&gt;&lt;/circle&gt;
&lt;circle cx="280" cy="851" r="11"&gt;&lt;/circle&gt;
&lt;/g&gt;
&lt;text x="250" y="893" text-anchor="middle" font-family="var(--serif)" font-size="34" fill="var(--muted)"&gt;3 months&lt;/text&gt;
&lt;g transform="translate(250 875)"&gt;
&lt;g stroke="var(--diagram-owned)" stroke-width="2.5"&gt;
&lt;line x1="0" y1="560" x2="-130" y2="320"&gt;&lt;/line&gt;
&lt;line x1="0" y1="560" x2="0" y2="320"&gt;&lt;/line&gt;
&lt;/g&gt;
&lt;line x1="0" y1="560" x2="130" y2="320" stroke="var(--diagram-warn)" stroke-width="2.5"&gt;&lt;/line&gt;
&lt;g stroke="var(--diagram-owned)" stroke-width="8" stroke-linecap="round"&gt;
&lt;line x1="-71.5" y1="428" x2="-104" y2="368"&gt;&lt;/line&gt;
&lt;line x1="0" y1="428" x2="0" y2="368"&gt;&lt;/line&gt;
&lt;/g&gt;
&lt;line x1="54.2" y1="460" x2="32.5" y2="500" stroke="var(--diagram-warn)" stroke-width="8" stroke-linecap="round" marker-end="url(#arrow-warn)"&gt;&lt;/line&gt;
&lt;use href="#bot-owned" transform="translate(-130 320) scale(2)"&gt;&lt;/use&gt;
&lt;use href="#bot-owned" transform="translate(0 320) scale(2)"&gt;&lt;/use&gt;
&lt;use href="#bot-warn" transform="translate(130 320) scale(2)"&gt;&lt;/use&gt;
&lt;use href="#laptop-alert" transform="translate(0 560) scale(2.1)"&gt;&lt;/use&gt;
&lt;/g&gt;

&lt;text x="750" y="811" text-anchor="middle" font-family="var(--serif)" font-size="36" font-weight="600" fill="var(--text)"&gt;Security&lt;/text&gt;
&lt;g fill="var(--diagram-owned)"&gt;
&lt;circle cx="675" cy="851" r="11"&gt;&lt;/circle&gt;
&lt;circle cx="705" cy="851" r="11"&gt;&lt;/circle&gt;
&lt;circle cx="735" cy="851" r="11"&gt;&lt;/circle&gt;
&lt;circle cx="765" cy="851" r="11"&gt;&lt;/circle&gt;
&lt;circle cx="795" cy="851" r="11"&gt;&lt;/circle&gt;
&lt;circle cx="825" cy="851" r="11"&gt;&lt;/circle&gt;
&lt;/g&gt;
&lt;text x="750" y="893" text-anchor="middle" font-family="var(--serif)" font-size="34" fill="var(--muted)"&gt;6 months&lt;/text&gt;
&lt;g transform="translate(750 875)"&gt;
&lt;g stroke="var(--diagram-owned)" stroke-width="2.5"&gt;
&lt;line x1="0" y1="560" x2="-130" y2="320"&gt;&lt;/line&gt;
&lt;line x1="0" y1="560" x2="0" y2="320"&gt;&lt;/line&gt;
&lt;line x1="0" y1="560" x2="130" y2="320"&gt;&lt;/line&gt;
&lt;/g&gt;
&lt;g stroke="var(--diagram-owned)" stroke-width="8" stroke-linecap="round"&gt;
&lt;line x1="-71.5" y1="428" x2="-104" y2="368"&gt;&lt;/line&gt;
&lt;line x1="0" y1="428" x2="0" y2="368"&gt;&lt;/line&gt;
&lt;/g&gt;
&lt;line x1="71.5" y1="428" x2="104" y2="368" stroke="var(--diagram-owned)" stroke-width="8" stroke-linecap="round"&gt;&lt;/line&gt;
&lt;rect x="-222" y="244" width="444" height="152" rx="76" fill="none" stroke="var(--diagram-owned)" stroke-width="4"&gt;&lt;/rect&gt;
&lt;use href="#bot-owned" transform="translate(-130 320) scale(2)"&gt;&lt;/use&gt;
&lt;use href="#bot-owned" transform="translate(0 320) scale(2)"&gt;&lt;/use&gt;
&lt;use href="#bot-owned" transform="translate(130 320) scale(2)"&gt;&lt;/use&gt;
&lt;use href="#laptop-ok" transform="translate(0 560) scale(2.1)"&gt;&lt;/use&gt;

&lt;/g&gt;

&lt;text x="1230" y="811" text-anchor="middle" font-family="var(--serif)" font-size="36" font-weight="600" fill="var(--text)"&gt;Compliance&lt;/text&gt;
&lt;g fill="var(--diagram-owned)"&gt;
&lt;circle cx="1200" cy="851" r="11"&gt;&lt;/circle&gt;
&lt;circle cx="1230" cy="851" r="11"&gt;&lt;/circle&gt;
&lt;circle cx="1260" cy="851" r="11"&gt;&lt;/circle&gt;
&lt;/g&gt;
&lt;text x="1230" y="893" text-anchor="middle" font-family="var(--serif)" font-size="34" fill="var(--muted)"&gt;3 months&lt;/text&gt;
&lt;g transform="translate(1230 875)"&gt;
&lt;g stroke="var(--diagram-owned)" stroke-width="2.5"&gt;
&lt;line x1="0" y1="560" x2="-130" y2="320"&gt;&lt;/line&gt;
&lt;line x1="0" y1="560" x2="0" y2="320"&gt;&lt;/line&gt;
&lt;line x1="0" y1="560" x2="130" y2="320"&gt;&lt;/line&gt;
&lt;/g&gt;
&lt;g stroke="var(--diagram-owned)" stroke-width="8" stroke-linecap="round"&gt;
&lt;line x1="-71.5" y1="428" x2="-104" y2="368"&gt;&lt;/line&gt;
&lt;line x1="0" y1="428" x2="0" y2="368"&gt;&lt;/line&gt;
&lt;/g&gt;
&lt;line x1="71.5" y1="428" x2="104" y2="368" stroke="var(--diagram-owned)" stroke-width="8" stroke-linecap="round"&gt;&lt;/line&gt;
&lt;rect x="-222" y="244" width="444" height="152" rx="76" fill="none" stroke="var(--diagram-owned)" stroke-width="4"&gt;&lt;/rect&gt;
&lt;use href="#bot-owned" transform="translate(-130 320) scale(2)"&gt;&lt;/use&gt;
&lt;use href="#bot-owned" transform="translate(0 320) scale(2)"&gt;&lt;/use&gt;
&lt;use href="#bot-owned" transform="translate(130 320) scale(2)"&gt;&lt;/use&gt;
&lt;use href="#laptop-ok" transform="translate(0 560) scale(2.1)"&gt;&lt;/use&gt;

&lt;g stroke="var(--muted)" stroke-width="1.5" fill="none"&gt;
&lt;line x1="-170" y1="70" x2="170" y2="70"&gt;&lt;/line&gt;
&lt;line x1="-170" y1="50" x2="-170" y2="90"&gt;&lt;/line&gt;
&lt;line x1="-85" y1="50" x2="-85" y2="90"&gt;&lt;/line&gt;
&lt;line x1="0" y1="50" x2="0" y2="90"&gt;&lt;/line&gt;
&lt;line x1="85" y1="50" x2="85" y2="90"&gt;&lt;/line&gt;
&lt;line x1="170" y1="50" x2="170" y2="90"&gt;&lt;/line&gt;
&lt;line x1="-170" y1="125" x2="170" y2="125"&gt;&lt;/line&gt;
&lt;line x1="-170" y1="105" x2="-170" y2="145"&gt;&lt;/line&gt;
&lt;line x1="-85" y1="105" x2="-85" y2="145"&gt;&lt;/line&gt;
&lt;line x1="0" y1="105" x2="0" y2="145"&gt;&lt;/line&gt;
&lt;line x1="85" y1="105" x2="85" y2="145"&gt;&lt;/line&gt;
&lt;line x1="170" y1="105" x2="170" y2="145"&gt;&lt;/line&gt;
&lt;line x1="-170" y1="180" x2="170" y2="180"&gt;&lt;/line&gt;
&lt;line x1="-170" y1="160" x2="-170" y2="200"&gt;&lt;/line&gt;
&lt;line x1="-85" y1="160" x2="-85" y2="200"&gt;&lt;/line&gt;
&lt;line x1="0" y1="160" x2="0" y2="200"&gt;&lt;/line&gt;
&lt;line x1="85" y1="160" x2="85" y2="200"&gt;&lt;/line&gt;
&lt;line x1="170" y1="160" x2="170" y2="200"&gt;&lt;/line&gt;
&lt;/g&gt;
&lt;g fill="var(--diagram-owned)"&gt;
&lt;circle cx="-127.5" cy="70" r="7"&gt;&lt;/circle&gt;
&lt;circle cx="-42.5" cy="70" r="7"&gt;&lt;/circle&gt;
&lt;circle cx="42.5" cy="70" r="7"&gt;&lt;/circle&gt;
&lt;circle cx="-127.5" cy="125" r="7"&gt;&lt;/circle&gt;
&lt;circle cx="42.5" cy="125" r="7"&gt;&lt;/circle&gt;
&lt;circle cx="-127.5" cy="180" r="7"&gt;&lt;/circle&gt;
&lt;circle cx="-42.5" cy="180" r="7"&gt;&lt;/circle&gt;
&lt;/g&gt;
&lt;g fill="var(--diagram-owned)"&gt;
&lt;circle cx="127.5" cy="70" r="8"&gt;&lt;/circle&gt;
&lt;circle cx="127.5" cy="125" r="8"&gt;&lt;/circle&gt;
&lt;circle cx="127.5" cy="180" r="8"&gt;&lt;/circle&gt;
&lt;/g&gt;
&lt;g fill="none" stroke="var(--diagram-owned)" stroke-width="2.5"&gt;
&lt;rect x="115.5" y="58" width="24" height="24" transform="rotate(45 127.5 70)"&gt;&lt;/rect&gt;
&lt;rect x="115.5" y="113" width="24" height="24" transform="rotate(45 127.5 125)"&gt;&lt;/rect&gt;
&lt;rect x="115.5" y="168" width="24" height="24" transform="rotate(45 127.5 180)"&gt;&lt;/rect&gt;
&lt;/g&gt;
&lt;g stroke="var(--diagram-owned)" stroke-width="1.5" fill="none"&gt;
&lt;line x1="-170" y1="70" x2="-130" y2="320"&gt;&lt;/line&gt;
&lt;line x1="-170" y1="125" x2="0" y2="320"&gt;&lt;/line&gt;
&lt;line x1="-170" y1="180" x2="130" y2="320"&gt;&lt;/line&gt;
&lt;/g&gt;
&lt;g fill="var(--diagram-owned)"&gt;
&lt;circle cx="-170" cy="70" r="3"&gt;&lt;/circle&gt;
&lt;circle cx="-170" cy="125" r="3"&gt;&lt;/circle&gt;
&lt;circle cx="-170" cy="180" r="3"&gt;&lt;/circle&gt;
&lt;/g&gt;
&lt;/g&gt;
&lt;/svg&gt;
&lt;/div&gt;
 &lt;div class="diagram-watermark"&gt;blog.bensoussan.de&lt;/div&gt;
 &lt;/div&gt;
 &lt;figcaption class="diagram-caption"&gt;&lt;span class="diagram-num"&gt;Figure 1.&lt;/span&gt; The same three robots at every stage, with one capability added each time. Before, nobody knows what is running on any of them. By the end, the fleet is reachable, reporting, protected and auditable. The pips under each name are the months that stage takes.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id="what-the-five-steps-cost"&gt;What the five steps cost&lt;/h2&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Step&lt;/th&gt;
					&lt;th&gt;What you can do once it&amp;rsquo;s done&lt;/th&gt;
					&lt;th&gt;Effort&lt;/th&gt;
					&lt;th&gt;Who carries it&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;Speed&lt;/td&gt;
					&lt;td&gt;Rebuild any version on demand, install it with one command&lt;/td&gt;
					&lt;td&gt;A month&lt;/td&gt;
					&lt;td&gt;An engineer you already have, part time&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Control&lt;/td&gt;
					&lt;td&gt;Reach every site remotely, change the robot app there by editing Git, ship a versioned OS image, roll either back&lt;/td&gt;
					&lt;td&gt;3 to 6 months&lt;/td&gt;
					&lt;td&gt;Full time&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Visibility&lt;/td&gt;
					&lt;td&gt;See a robot degrading before the customer phones&lt;/td&gt;
					&lt;td&gt;3 months&lt;/td&gt;
					&lt;td&gt;Full time&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Security&lt;/td&gt;
					&lt;td&gt;Only builds you approved run; revoke one key remotely&lt;/td&gt;
					&lt;td&gt;6 months&lt;/td&gt;
					&lt;td&gt;Full time&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Compliance&lt;/td&gt;
					&lt;td&gt;Answer what ran where, and when from your office&lt;/td&gt;
					&lt;td&gt;3 months&lt;/td&gt;
					&lt;td&gt;Full time&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Step 1 is small enough to start without approving a budget. You just need pipelines turn green. For that, you need to understand how to build the software and train employees to use the CI/CD workflow.&lt;/p&gt;</description></item><item><title>Updating Robots in the Field</title><link>https://attheedge.dev/post/2026-08-11-updating-robots-in-the-field/</link><pubDate>Tue, 11 Aug 2026 10:00:00 +0200</pubDate><guid>https://attheedge.dev/post/2026-08-11-updating-robots-in-the-field/</guid><description>&lt;p&gt;Ask a robotics company, especially a young one, how software gets onto a robot that&amp;rsquo;s already at a customer site, and the answer usually is someone driving there, accessing the robot, pulling the code and getting it to run, watching the robot move for a minute, then moving on to the next one.&lt;/p&gt;
&lt;p&gt;The reason is lack of awareness rather than carelessness. How many robotics companies have a DevOps engineer on the team? Most are led by roboticists and researchers who can make anything happen on a few robots. So a great deal of sophisticated software gets written, and then moved around manually.&lt;/p&gt;</description></item><item><title>PROVEN: Six Checks Against AI Slop</title><link>https://attheedge.dev/post/2026-08-05-six-checks-against-ai-slop/</link><pubDate>Wed, 05 Aug 2026 00:00:00 +0000</pubDate><guid>https://attheedge.dev/post/2026-08-05-six-checks-against-ai-slop/</guid><description>&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/immich-app/immich/pull/30514"&gt;feat(transcription): automatic video subtitle generation via faster-whisper #30514&lt;/a&gt;&lt;/strong&gt; — closed · 17 commits · 3,035 tests passing · 0 lint errors · &lt;code&gt;auto-closed:llm&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;My AI-assisted pull request to &lt;a href="https://github.com/immich-app/immich"&gt;immich&lt;/a&gt;, an open-source project, was closed within minutes today because an LLM was involved. Their policy forbids it:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We ask you not to open PRs generated with an LLM. We find that code generated like this tends to need a large amount of back-and-forth, which is a very inefficient use of our time.
We cannot have any confidence in an LLM, so if it&amp;rsquo;s non-trivial for us to verify it works and you don&amp;rsquo;t show that you fully understand all implications of the change, reviewing the PR is not worth our time.&lt;/p&gt;</description></item><item><title>Zero Overhead</title><link>https://attheedge.dev/post/2026-08-04-zero-overhead/</link><pubDate>Tue, 04 Aug 2026 00:00:00 +0000</pubDate><guid>https://attheedge.dev/post/2026-08-04-zero-overhead/</guid><description>&lt;p&gt;I&amp;rsquo;ve never kept a blog. Every time I considered it, the overhead argument
won: a platform to maintain, a hosting bill, and posts that would have to be
written from scratch.
Not worth it.&lt;/p&gt;
&lt;p&gt;Two things changed this year, so the math finally works.&lt;/p&gt;
&lt;h2 id="who-i-am"&gt;Who I am&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;m &lt;a href="https://resume.bensoussan.de"&gt;David Bensoussan&lt;/a&gt;, a software engineer.
Most of what I build outside work lives in one monorepo: the apps, the
infrastructure that runs them, and the CI in between. The infrastructure part
is a homelab — two second-hand Debian machines running about 45 services:
photos, media, documents, monitoring, an LLM server on an old GPU, and the CI
runners themselves. The full topology and progress log is at
&lt;a href="https://homelab.bensoussan.de"&gt;homelab.bensoussan.de&lt;/a&gt; if you want the
inventory. Everything is provisioned with Ansible, and every significant
decision gets written down.&lt;/p&gt;</description></item></channel></rss>