<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Reisinger.Tech</title>
    <description>Home on the web for Billy Reisinger</description>
    <link>https://reisinger.tech</link>
    <atom:link href="https://reisinger.tech/feed.xml" rel="self" type="application/rss+xml"/>
    <lastBuildDate>Tue, 02 Jun 2026 00:00:00 -0500</lastBuildDate>
    
    <item>
      <title>I vibe-coded my bookshelf speakers into bluetooth speakers</title>
      <link>https://reisinger.tech/updates/2026/06/02/bluetooth-speakers/</link>
      <guid isPermaLink="true">https://reisinger.tech/updates/2026/06/02/bluetooth-speakers/</guid>
      <pubDate>Tue, 02 Jun 2026 00:00:00 -0500</pubDate>
      <description>&lt;p&gt;I had a pair of BIC dv62si bookshelf speakers in my office that were doing nothing, connected to nothing, collecting dust. I also had a drawer full of Raspberry Pis. The obvious move was to turn them into a wireless stereo pair that I could stream to from my phone, so I did that — with Claude doing most of the heavy architectural lifting while I did the wiring and debugging.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;#approach&quot;&gt;Approach&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#architecture&quot;&gt;Architecture&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#parts&quot;&gt;Parts&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#power&quot;&gt;Power&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#audio-stack&quot;&gt;Audio Stack&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#pairing-button-and-leds&quot;&gt;Pairing Button and LEDs&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#right-speaker&quot;&gt;Right Speaker&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#web-interface&quot;&gt;Web Interface&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;approach&quot;&gt;Approach&lt;/h2&gt;

&lt;p&gt;Before touching any hardware, I used Claude to work through three possible architectures. The first used two Pis connected over WiFi with Snapcast handling audio synchronization. The second used a Pi and an ESP32 microcontroller communicating over UDP, which would have required writing custom firmware in C. The third relayed audio from the Pi to an ESP32 over a second Bluetooth connection, an interesting topology that would have been hard to keep in sync. I went with the first approach. Snapcast was designed exactly for this problem — synchronized multi-room audio — and it meant no custom firmware, standard packages, and SSH access to both devices for debugging.&lt;/p&gt;

&lt;h2 id=&quot;architecture&quot;&gt;Architecture&lt;/h2&gt;

&lt;p&gt;The left speaker (Pi 4B) is the master. Your phone connects to it over Bluetooth and streams audio via the A2DP profile. The Pi receives the audio through BlueZ and PipeWire and writes it into a named pipe on disk. A Snapcast server reads from that pipe, adds precise timestamps to the audio stream, and broadcasts it over WiFi.&lt;/p&gt;

&lt;p&gt;The right speaker (Pi 3 A+) runs a Snapcast client that connects to the left speaker over the local network, receives the timestamped stream, and plays it back through its own USB audio adapter and amplifier. Snapcast’s synchronization keeps both speakers within about a millisecond of each other — close enough that holding them side by side produces a single clean sound rather than an echo.&lt;/p&gt;

&lt;figure&gt;
&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 608 200&quot; style=&quot;max-width:100%;display:block;&quot;&gt;
  &lt;defs&gt;
    &lt;marker id=&quot;arr&quot; markerWidth=&quot;7&quot; markerHeight=&quot;5&quot; refX=&quot;6&quot; refY=&quot;2.5&quot; orient=&quot;auto&quot;&gt;
      &lt;polygon points=&quot;0 0,7 2.5,0 5&quot; class=&quot;ah&quot; /&gt;
    &lt;/marker&gt;
  &lt;/defs&gt;
  &lt;style&gt;
    .box{fill:#f5f5ef;stroke:#555;stroke-width:1;}
    .ah{fill:#555;}
    .ln{stroke:#555;stroke-width:1.5;fill:none;marker-end:url(#arr);}
    .h{font-size:11.5px;fill:#222;text-anchor:middle;dominant-baseline:middle;}
    .sub{font-size:9.5px;fill:#888;text-anchor:middle;dominant-baseline:middle;}
    .el{font-size:9px;fill:#777;text-anchor:middle;}
    @media(prefers-color-scheme:dark){
      .box{fill:#252525;stroke:#999;}
      .ah{fill:#999;}
      .ln{stroke:#999;}
      .h{fill:#ddd;}
      .sub{fill:#aaa;}
      .el{fill:#aaa;}
    }
  &lt;/style&gt;

  &lt;!-- Phone --&gt;
  &lt;rect x=&quot;5&quot; y=&quot;47&quot; width=&quot;76&quot; height=&quot;42&quot; rx=&quot;3&quot; class=&quot;box&quot; /&gt;
  &lt;text x=&quot;43&quot; y=&quot;64&quot; class=&quot;h&quot;&gt;Phone&lt;/text&gt;
  &lt;text x=&quot;43&quot; y=&quot;79&quot; class=&quot;sub&quot;&gt;source&lt;/text&gt;

  &lt;!-- Pi 4B --&gt;
  &lt;rect x=&quot;122&quot; y=&quot;47&quot; width=&quot;104&quot; height=&quot;42&quot; rx=&quot;3&quot; class=&quot;box&quot; /&gt;
  &lt;text x=&quot;174&quot; y=&quot;64&quot; class=&quot;h&quot;&gt;Pi 4B&lt;/text&gt;
  &lt;text x=&quot;174&quot; y=&quot;79&quot; class=&quot;sub&quot;&gt;left · master&lt;/text&gt;

  &lt;!-- USB DAC L --&gt;
  &lt;rect x=&quot;272&quot; y=&quot;47&quot; width=&quot;76&quot; height=&quot;42&quot; rx=&quot;3&quot; class=&quot;box&quot; /&gt;
  &lt;text x=&quot;310&quot; y=&quot;64&quot; class=&quot;h&quot;&gt;USB DAC&lt;/text&gt;
  &lt;text x=&quot;310&quot; y=&quot;79&quot; class=&quot;sub&quot;&gt;SABRENT&lt;/text&gt;

  &lt;!-- Fosi Amp --&gt;
  &lt;rect x=&quot;394&quot; y=&quot;47&quot; width=&quot;84&quot; height=&quot;42&quot; rx=&quot;3&quot; class=&quot;box&quot; /&gt;
  &lt;text x=&quot;436&quot; y=&quot;64&quot; class=&quot;h&quot;&gt;Fosi Amp&lt;/text&gt;
  &lt;text x=&quot;436&quot; y=&quot;79&quot; class=&quot;sub&quot;&gt;TDA7498E&lt;/text&gt;

  &lt;!-- Left Speaker --&gt;
  &lt;rect x=&quot;525&quot; y=&quot;47&quot; width=&quot;78&quot; height=&quot;42&quot; rx=&quot;3&quot; class=&quot;box&quot; /&gt;
  &lt;text x=&quot;564&quot; y=&quot;64&quot; class=&quot;h&quot;&gt;Left&lt;/text&gt;
  &lt;text x=&quot;564&quot; y=&quot;79&quot; class=&quot;sub&quot;&gt;BIC dv62si&lt;/text&gt;

  &lt;!-- Pi 3 A+ --&gt;
  &lt;rect x=&quot;122&quot; y=&quot;143&quot; width=&quot;104&quot; height=&quot;42&quot; rx=&quot;3&quot; class=&quot;box&quot; /&gt;
  &lt;text x=&quot;174&quot; y=&quot;160&quot; class=&quot;h&quot;&gt;Pi 3 A+&lt;/text&gt;
  &lt;text x=&quot;174&quot; y=&quot;175&quot; class=&quot;sub&quot;&gt;right · client&lt;/text&gt;

  &lt;!-- USB DAC R --&gt;
  &lt;rect x=&quot;272&quot; y=&quot;143&quot; width=&quot;76&quot; height=&quot;42&quot; rx=&quot;3&quot; class=&quot;box&quot; /&gt;
  &lt;text x=&quot;310&quot; y=&quot;160&quot; class=&quot;h&quot;&gt;USB DAC&lt;/text&gt;
  &lt;text x=&quot;310&quot; y=&quot;175&quot; class=&quot;sub&quot;&gt;SABRENT&lt;/text&gt;

  &lt;!-- TPA3116D2 Amp --&gt;
  &lt;rect x=&quot;394&quot; y=&quot;143&quot; width=&quot;84&quot; height=&quot;42&quot; rx=&quot;3&quot; class=&quot;box&quot; /&gt;
  &lt;text x=&quot;436&quot; y=&quot;160&quot; class=&quot;h&quot;&gt;TPA Amp&lt;/text&gt;
  &lt;text x=&quot;436&quot; y=&quot;175&quot; class=&quot;sub&quot;&gt;TPA3116D2&lt;/text&gt;

  &lt;!-- Right Speaker --&gt;
  &lt;rect x=&quot;525&quot; y=&quot;143&quot; width=&quot;78&quot; height=&quot;42&quot; rx=&quot;3&quot; class=&quot;box&quot; /&gt;
  &lt;text x=&quot;564&quot; y=&quot;160&quot; class=&quot;h&quot;&gt;Right&lt;/text&gt;
  &lt;text x=&quot;564&quot; y=&quot;175&quot; class=&quot;sub&quot;&gt;BIC dv62si&lt;/text&gt;

  &lt;!-- Top row arrows --&gt;
  &lt;line x1=&quot;81&quot; y1=&quot;68&quot; x2=&quot;117&quot; y2=&quot;68&quot; class=&quot;ln&quot; /&gt;
  &lt;text x=&quot;99&quot; y=&quot;42&quot; class=&quot;el&quot;&gt;BT A2DP&lt;/text&gt;

  &lt;line x1=&quot;226&quot; y1=&quot;68&quot; x2=&quot;267&quot; y2=&quot;68&quot; class=&quot;ln&quot; /&gt;
  &lt;line x1=&quot;348&quot; y1=&quot;68&quot; x2=&quot;389&quot; y2=&quot;68&quot; class=&quot;ln&quot; /&gt;
  &lt;line x1=&quot;478&quot; y1=&quot;68&quot; x2=&quot;520&quot; y2=&quot;68&quot; class=&quot;ln&quot; /&gt;

  &lt;!-- Vertical: Pi 4B → Pi 3 A+ (Snapcast/WiFi) --&gt;
  &lt;line x1=&quot;174&quot; y1=&quot;89&quot; x2=&quot;174&quot; y2=&quot;138&quot; class=&quot;ln&quot; /&gt;
  &lt;text x=&quot;183&quot; y=&quot;108&quot; class=&quot;el&quot; text-anchor=&quot;start&quot;&gt;Snapcast&lt;/text&gt;
  &lt;text x=&quot;183&quot; y=&quot;120&quot; class=&quot;el&quot; text-anchor=&quot;start&quot;&gt;over WiFi&lt;/text&gt;

  &lt;!-- Bottom row arrows --&gt;
  &lt;line x1=&quot;226&quot; y1=&quot;164&quot; x2=&quot;267&quot; y2=&quot;164&quot; class=&quot;ln&quot; /&gt;
  &lt;line x1=&quot;348&quot; y1=&quot;164&quot; x2=&quot;389&quot; y2=&quot;164&quot; class=&quot;ln&quot; /&gt;
  &lt;line x1=&quot;478&quot; y1=&quot;164&quot; x2=&quot;520&quot; y2=&quot;164&quot; class=&quot;ln&quot; /&gt;
&lt;/svg&gt;
&lt;figcaption&gt;Audio signal flow. The phone connects once to the left speaker over Bluetooth; Snapcast distributes the stream to the right speaker over WiFi with millisecond-level sync.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The two Pis only need to be on the same WiFi network. There is no wired connection between them. The right speaker finds the left at boot using mDNS — it looks up &lt;code class=&quot;highlighter-rouge&quot;&gt;left-speaker.local&lt;/code&gt;, connects, and starts playing as soon as audio is available. The web interface also runs on the left Pi and controls the whole system from one place.&lt;/p&gt;

&lt;h2 id=&quot;parts&quot;&gt;Parts&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Left speaker (master):&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Raspberry Pi 4B&lt;/li&gt;
  &lt;li&gt;Fosi TDA7498E class-D amplifier&lt;/li&gt;
  &lt;li&gt;SABRENT AU-MMSA USB audio adapter&lt;/li&gt;
  &lt;li&gt;BIC dv62si speaker&lt;/li&gt;
  &lt;li&gt;24V/4.5A power supply&lt;/li&gt;
  &lt;li&gt;25W DC-DC buck converter (24V → 5V for the Pi)&lt;/li&gt;
  &lt;li&gt;Ground loop noise isolator (3.5mm inline, between USB adapter and amp input)&lt;/li&gt;
  &lt;li&gt;Green LED (power indicator), blue LED (Bluetooth status), momentary push button (pairing)&lt;/li&gt;
  &lt;li&gt;Two 330Ω resistors&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Right speaker (client):&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Raspberry Pi 3 A+ — salvaged from a Pi Stomp v2 guitar pedal that had been sitting unused in a box for a year and a half&lt;/li&gt;
  &lt;li&gt;Clyxgs TPA3116D2 class-D amplifier&lt;/li&gt;
  &lt;li&gt;SABRENT AU-MMSA USB audio adapter&lt;/li&gt;
  &lt;li&gt;BIC dv62si speaker&lt;/li&gt;
  &lt;li&gt;12V/3A power supply (repurposed Linksys router adapter)&lt;/li&gt;
  &lt;li&gt;25W DC-DC buck converter (12V → 5V for the Pi)&lt;/li&gt;
  &lt;li&gt;Ground loop noise isolator (3.5mm inline, between USB adapter and amp input)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both speakers use the same USB audio adapter for consistency — identical ALSA device names on both Pis, no HAT overlays or device tree configuration required. The original plan used an AudioInjector sound card HAT for the right speaker, which would have required configuring a device tree overlay. I switched to the USB adapter early on and it was immediately simpler: plug it in, and Linux recognizes it as a standard USB audio device with no configuration.&lt;/p&gt;

&lt;h2 id=&quot;power&quot;&gt;Power&lt;/h2&gt;

&lt;p&gt;Each speaker runs off one wall outlet. The power supply feeds the amplifier directly and also feeds the buck converter, which steps voltage down to 5V for the Pi. The Pi is powered via its GPIO header rather than the USB port, which bypasses the onboard polyfuse — I measured the buck converter output with a multimeter before connecting it, and held my breath when I plugged it in for the first time.&lt;/p&gt;

&lt;p&gt;The left speaker’s 24V/4.5A supply came with the Fosi amp. For the right speaker, I went through a box of old power supplies and found a 12V/3A adapter from a decommissioned Linksys router. It fit the barrel connector, the voltage is within the TPA3116D2’s operating range, and it works.&lt;/p&gt;

&lt;p&gt;One wiring mistake that cost me an hour: I used 28 AWG stranded wire for the buck converter to GPIO header run. 28 AWG looks fine sitting at idle, but it has enough resistance that under load — specifically, the moment a Bluetooth client connected and the Pi’s CPU and WiFi radio spun up together — the voltage sagged below the Pi’s minimum and it rebooted. This happened reliably enough to be confusing, because the Pi came back up cleanly every time and nothing in the logs pointed to power. The fix was replacing the run with heavier gauge wire, after which the reboots stopped entirely. The clue was an undervoltage warning in the kernel log — &lt;code class=&quot;highlighter-rouge&quot;&gt;dmesg&lt;/code&gt; showed the lightning bolt symbol event right before each reboot, which pointed directly at the power rail rather than software. For any GPIO power feed, use at least 22 AWG.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/bluetooth-speakers/IMG_7516.jpeg&quot; alt=&quot;Left speaker wiring&quot; style=&quot;border-radius: 4px;&quot; /&gt;
  &lt;figcaption&gt;Left speaker. The Fosi TDA7498E amp is the gray box with audio ports; the buck converter is the heat sink thing; the Pi 4B is the black box on top of the Fosi. Orange WAGO connectors split the 24V rail. You can also see the ground loop isolater connected to the Fosi.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/bluetooth-speakers/IMG_7518.jpeg&quot; alt=&quot;Left speaker rear angle&quot; style=&quot;border-radius: 4px;&quot; /&gt;
  &lt;figcaption&gt;The buck converter is the silver-heatsink board at lower left. DuPont wires run from it to the GPIO header on the Pi.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;audio-stack&quot;&gt;Audio Stack&lt;/h2&gt;

&lt;p&gt;The left Pi receives Bluetooth A2DP audio from a phone via BlueZ and PipeWire. A bridge script monitors the PipeWire session for a Bluetooth source, then pipes the audio stream into a named FIFO file on disk. Snapcast server reads from the FIFO and broadcasts a precisely timestamped stream over WiFi. The right Pi runs a Snapcast client that connects to the left speaker and plays back the stream through its USB audio adapter. Both speakers stay within about one millisecond of each other.&lt;/p&gt;

&lt;p&gt;After getting audio working, both speakers had an audible hum — a classic ground loop, caused by the USB audio adapter and the amplifier sharing a ground path through the power rail. The fix was a pair of ground loop noise isolators (2 Packs Ground Loop Noise Isolator for Car Audio/Home Stereo System) — 3.5mm inline adapters that sit between the USB adapter’s headphone output and the amp input on each speaker. The hum disappeared immediately.&lt;/p&gt;

&lt;p&gt;The trickiest part was the bridge script. When a phone disconnects, the PipeWire Bluetooth source disappears, which kills the audio pipe. Rather than watching for D-Bus events to restart it, the script just polls every two seconds and relaunches &lt;code class=&quot;highlighter-rouge&quot;&gt;pacat&lt;/code&gt; when a source reappears. Unglamorous, but reliable.&lt;/p&gt;

&lt;p&gt;Another non-obvious issue: &lt;code class=&quot;highlighter-rouge&quot;&gt;pactl&lt;/code&gt; has to run as the same user who owns the PipeWire session. Running the bridge service as root causes it to silently connect to an empty PipeWire instance. The systemd unit sets &lt;code class=&quot;highlighter-rouge&quot;&gt;User=&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;XDG_RUNTIME_DIR&lt;/code&gt; explicitly to match the logged-in user.&lt;/p&gt;

&lt;h2 id=&quot;pairing-button-and-leds&quot;&gt;Pairing Button and LEDs&lt;/h2&gt;

&lt;p&gt;Without a screen or keyboard, there needed to be some way to put the left speaker into Bluetooth pairing mode. A push button wired to GPIO 22 handles that. Press it, and the speaker becomes discoverable for 60 seconds. A Python daemon (running as a systemd service) handles the button input via &lt;code class=&quot;highlighter-rouge&quot;&gt;gpiozero&lt;/code&gt; and monitors BlueZ connection events over D-Bus to keep the LED states accurate. The green LED comes on when the Pi boots. The blue LED blinks during pairing and goes solid when a device is connected.&lt;/p&gt;

&lt;p&gt;The only gotcha here was threading: the GLib main loop (needed for D-Bus events) and the gpiozero button callback both need to run concurrently. Moving the 60-second pairing timeout into a background thread kept the main loop unblocked.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/bluetooth-speakers/IMG_7515.jpeg&quot; alt=&quot;Left speaker front view&quot; style=&quot;border-radius: 4px;&quot; /&gt;
  &lt;figcaption&gt;Left speaker from the front. The green power LED and blue status LED are visible to the left. The yellow button above them triggers pairing mode.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;right-speaker&quot;&gt;Right Speaker&lt;/h2&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/bluetooth-speakers/IMG_7517.jpeg&quot; alt=&quot;Right speaker&quot; style=&quot;border-radius: 4px;&quot; /&gt;
  &lt;figcaption&gt;Right speaker. The Pi 3 A+ is the green board at top center, freed from its previous life inside a guitar pedal. The TPA3116D2 amp is the blue board below it.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The right speaker has no button and no LEDs — it connects to the left speaker automatically on boot via mDNS and starts playing as soon as audio is available. Both speakers come up on their own after a power cycle. The whole system is controlled from one place: the left speaker, either by pressing its button or opening the web interface.&lt;/p&gt;

&lt;p&gt;One wiring step that took longer than expected: connecting the USB audio adapter to the TPA3116D2’s JST audio input. The JST connector has three pins — LIN, GND, and RIN — and the 3.5mm plug has tip (left channel), ring (right channel), and sleeve (ground). Since the right speaker plays only the right channel, ring goes to RIN and sleeve goes to GND. The tip is left unconnected. Getting that wrong the first time produced silence, which was a good outcome compared to the alternatives.&lt;/p&gt;

&lt;h2 id=&quot;web-interface&quot;&gt;Web Interface&lt;/h2&gt;

&lt;p&gt;Once the speakers were working, I added a web interface — a FastAPI app running on the left Pi at &lt;code class=&quot;highlighter-rouge&quot;&gt;http://left-speaker.local:8080&lt;/code&gt;. It shows the currently playing track with album art (via BlueZ MPRIS), lets me adjust each speaker’s volume independently (via Snapcast JSON-RPC), and includes a 10-band graphic EQ (ffmpeg filter chain applied to the audio bridge, with about one second of silence when settings change). There are transport controls, a test tone generator for verifying each speaker independently, and reboot buttons for both Pis.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/bluetooth-speakers/web-interface.png&quot; alt=&quot;Web interface&quot; style=&quot;border-radius: 4px;&quot; /&gt;
  &lt;figcaption&gt;Web interface showing Gilad Hekselman&apos;s &lt;em&gt;Ask for Chaos&lt;/em&gt; playing. Volume sliders control each speaker independently via Snapcast.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;This was a genuinely fun build. The thing I was most apprehensive about — the wiring — turned out to be the most satisfying part, mostly because I used WAGO lever-nut connectors throughout instead of soldering. I did almost no soldering on this project: just the two LEDs and the pairing button. Everything else snaps into a WAGO and can be pulled apart and reconfigured without any heat. I’ll be using them in every future hardware project.&lt;/p&gt;

&lt;p&gt;The AI-assisted workflow held up well. Claude helped design the architecture, wrote the setup scripts, and caught wiring issues before they became problems. My job was to sit at the bench, execute, and debug what didn’t work — which turned out to be a good division of labor.&lt;/p&gt;

&lt;p&gt;Next up: adding a subwoofer.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Stop measuring AI adoption and focus on outcomes</title>
      <link>https://reisinger.tech/updates/2025/12/18/stop-measuring-ai/</link>
      <guid isPermaLink="true">https://reisinger.tech/updates/2025/12/18/stop-measuring-ai/</guid>
      <pubDate>Thu, 18 Dec 2025 09:05:00 -0600</pubDate>
      <description>&lt;p&gt;As a software engineer at a large company, I have access to the latest and best AI tools available on the market at no cost to me. As
I gain experience with these tools, and as they continue to mature, I’m starting to get a handle on this new way of working. Like many others
I have become a code steward - a full-time reviewer. I’m also increasingly aware that I am inept at describing what I want the 
tool to accomplish. In fact, this has always been the difficult part of software engineering in my career. Most of my time “coding” is actually 
spent looking at things - observing, testing, reading documentation, and looking at others’ code.&lt;/p&gt;

&lt;p&gt;As a senior engineer, I don’t spend most of my time “coding” though. I spend most of it planning, documenting, and reviewing other people’s work. 
I’m not currently a manager, but as one it would be easy to soak in the numbers of “AI compliance” and feel as though a team is making progress. 
But, now, more than ever, it is easier to write the wrong code than it has ever been. The extra time we saved by using an AI to write code for us - where did it go? Did it go 
into planning, documenting, and reviewing instead? Well, unfortunately, no - it’s going into code review and correcting the AI when it hallucinates.&lt;/p&gt;

&lt;p&gt;This is ok I guess - I certainly had moments before the LLM revolution where I coded the wrong thing for a day, only to find out later - 
after, you know, &lt;em&gt;talking to a real person&lt;/em&gt; - that a completely different approach was necessary or that my understanding of the problem 
was askew and I needed to start over again. The same is true with AI - it often takes multiple iterations to get something right. What’s changed
is that I no longer have a complete mental model of the technical approach to solve the problem before tackling it - I delegate this 
responsibility to the AI, in the hope that it can save me from some hard thinking. But often, the AI needs to be guided and corrected, 
which means I end up doing the hard thinking after all.&lt;/p&gt;

&lt;p&gt;If we still care about delighting people with our work, making customers happy and delivering value, we should measure
&lt;em&gt;those&lt;/em&gt; things hard. Measuring AI adoption is not a proxy for true value creation; it’s a shareholder panic indicator.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>I was a fan of Shift Left before it was cool</title>
      <link>https://reisinger.tech/updates/2025/09/04/before-shift-left/</link>
      <guid isPermaLink="true">https://reisinger.tech/updates/2025/09/04/before-shift-left/</guid>
      <pubDate>Thu, 04 Sep 2025 22:55:09 -0500</pubDate>
      <description>&lt;p&gt;Early in my programming career, I was lucky to work with a manager who was a champion of Scrum, Extreme Programming, and Test-Driven Development, and dogmatic in his approach to all three. The team was cross-functional, comprised of engineers with various skillsets and QA professionals. We defined the scope of our work with QA in the room, incorporating their feedback on how a feature would be tested and how long that might take into the scope of the task. As a team, we decided we were not done with a task until the QA members of the team signed off that it was working.&lt;/p&gt;

&lt;p&gt;When a developer picked up a task, the final step after deploying the code to the test environment was to inform the assigned QA tester that it was ready to be tested. If she didn&apos;t like something about it, a short whiteboard entry would be created and we would have a conversation about what to do. We would make the necessary changes right then and there, re-deploying the code to the test environment as many times as it took to get it right. There was literally no delay between the tester&apos;s feedback and the code being fixed, because it wasn&apos;t done until the tester signed off on it, and we worked on tasks in a tight 2-week sprint cycle.&lt;/p&gt;

&lt;p&gt;From this small team of 4 engineers and 2 testers came a ground-up rewrite of the company&apos;s flagship product. The team began migrating customers to the new application months after I joined, and we completed migrating customers a few years later - after we achieved enough feature parity for even the thorniest customer to be satisfied. It&apos;s no exaggeration to say that we delivered a product of the highest quality, and we did it in a fraction of the time it would have taken if we had not owned the quality of our output as a team. &lt;/p&gt;

&lt;p&gt;Needless to say, the units of work had to be small enough to fit into a sprint - breaking down tasks like this was an art as much as a science; however, one clear necessity was that there needed to be &lt;em&gt;something&lt;/em&gt; for a tester to interact with to test the output of our work. In other words, we were forced to focus on tasks that added user value, and they tended to be small vertical slices of functionality rather than large features that spanned multiple components of the application.&lt;/p&gt;

&lt;p&gt;Years later, I heard this practice described as &quot;shifting left,&quot; and it dawned on me that this particular Agile team was doing this practice before it was cool. &lt;/p&gt;
</description>
    </item>
    
  </channel>
</rss>
