<?xml version="1.0" encoding="utf-8" standalone="yes"?>

<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Thomas Letan's Blog - vibecoding</title>
    <link>https://soap.coffee/~lthms/tags/vibecoding.html</link>
    <description>Posts tagged "vibecoding"</description>
    <atom:link href="https://soap.coffee/~lthms/tags/vibecoding.xml" rel="self"
               type="application/rss+xml" />
    
    
    <item>
      <title>What happened in February 2026?</title>
      <link>https://soap.coffee/~lthms/posts/february-2026.html</link>
      <guid>https://soap.coffee/~lthms/posts/february-2026.html</guid>
      <pubDate>March 8, 2026</pubDate>
      <description>
        
        &lt;h1&gt;What happened in February 2026?&lt;/h1&gt;&lt;div id=&quot;tags-list&quot;&gt;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#tag&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&amp;nbsp;&lt;a href=&quot;/~lthms/tags/meta.html&quot; class=&quot;tag hover-sky&quot; marked=&quot;&quot;&gt;meta&lt;/a&gt; &lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#tag&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&amp;nbsp;&lt;a href=&quot;/~lthms/tags/vibecoding.html&quot; class=&quot;tag hover-peach&quot; marked=&quot;&quot;&gt;vibecoding&lt;/a&gt; &lt;/div&gt;
&lt;p&gt;Time flies. February was a productive month, and I got many things done—with
the help of my &lt;a href=&quot;https://github.com/letan-assistant&quot; class=&quot;hover-lavender&quot; marked=&quot;&quot;&gt;coding assistant&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;, that is. I haven’t joined the “I
haven’t written a single line of code for quite some time” flock just yet, but
I am close. At the same time, I feel like I’m still locked in beginner mode.
Like I have still to &lt;em&gt;really&lt;/em&gt; tap into what agentic tools like Claude Code have
to offer.&lt;/p&gt;
&lt;p&gt;For now, the tools may have changed, but the journeys feel somewhat the same as
they used to a few months back. It’s a strange feeling, and I am still trying
to wrap my head around it.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Yet Another Claude Code Wrapper&lt;/h2&gt;
&lt;p&gt;In my previous retrospective, I mentioned CeCe and Vee. The former is a plugin
for turning Anthropic’s model into a “modal” agent that I had vibe “prompted”
with Claude itself. The latter was a second take at the exact same concept.&lt;/p&gt;
&lt;p&gt;A month later, Vee has evolved quite a bit, to a point where the “modal”
aspect of the project lost the front seat. More precisely, Vee has become a
session multiplexer for Claude Code, with built-in support for spawning it in
ephemeral containers with &lt;code class=&quot;hljs&quot;&gt;--dangerously-skip-permissions&lt;/code&gt; enabled. It is also
a playground for me to experiment with the agent. For instance, I built a
feedback system where I can point it to the good and bad things it’s doing—my
feedbacks get saved in a SQLite database and appended in the system prompt
during the next sessions. I’ve also tried to set up a knowledge base with
embeddings. Both approaches have their benefits, but I don’t think I have clear
evidence that they can be a game changer.&lt;/p&gt;
&lt;p&gt;You may remember Vee started out of my frustration to losing control of CeCe’s
internals. Well, Claude Code took over Vee’s codebase quite quickly as well.
But this time, the output was a lot more satisfying. This time, I leaned into
playing the role of a Tech Lead and a QA tester. Seeing the project taking
shape and the tool I wanted becoming a reality that quickly has clear upsides.
I also find it interesting that I have both a fairly good understanding of how
the software works overall—I called most of the architectural choices—&lt;em&gt;and&lt;/em&gt;
very little insight on how the code itself is written. It reminds me of
&lt;code class=&quot;hljs language-bash&quot;&gt;&lt;span class=&quot;hljs-variable&quot;&gt;$WORK&lt;/span&gt;&lt;/code&gt;, where I am involved in some projects without being a direct IC.&lt;/p&gt;
&lt;h2&gt;I Built Myself a Cloudlab&lt;/h2&gt;
&lt;p&gt;In the first half of the month, a colleague of mine was tasked with building
“staging environments &lt;em&gt;à la demande&lt;/em&gt;” for the engineers working on
&lt;code class=&quot;hljs language-bash&quot;&gt;&lt;span class=&quot;hljs-variable&quot;&gt;$WORK&lt;/span&gt;&lt;/code&gt;’s Next Big Thing™. Their answer was a managed Kubernetes cluster
and a bunch of Helm charts—and a small CLI tool to deploy the latter onto the
former. A few hours later, I started building myself a &lt;a href=&quot;https://k3s.io&quot; class=&quot;hover-coral&quot; marked=&quot;&quot;&gt;k3s&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; cluster. &lt;em&gt;It’ll be
a fun way to get familiar with the stack,&lt;/em&gt; I thought. What emerged almost
resembles a managed cluster of sorts. I called it &lt;a href=&quot;https://github.com/lthms/elsa&quot; class=&quot;hover-lavender&quot; marked=&quot;&quot;&gt;&lt;code class=&quot;hljs&quot;&gt;elsa&lt;/code&gt;&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;, and it now
hosts &lt;a href=&quot;https://github.com/lthms/cloud-lab&quot; class=&quot;hover-rose&quot; marked=&quot;&quot;&gt;this very website along with a few other self-hosted services I had
under my radar for a while&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;markdown-alert markdown-alert-note&quot;&gt;&lt;p class=&quot;markdown-alert-title&quot;&gt;&lt;svg class=&quot;octicon octicon-info mr-2&quot; viewbox=&quot;0 0 16 16&quot; version=&quot;1.1&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;Note&lt;/p&gt;&lt;p&gt;Yes, GitHub Pages and the like remain a better, cheaper, superior even way to
host my website. But where is the fun in that? 😁&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;This may come as a surprise, considering &lt;a href=&quot;/~lthms/posts/i-cannot-ssh-into-my-server-anymore.html&quot; class=&quot;hover-sky&quot; marked=&quot;&quot;&gt;I had migrated my website to a new
setup called &lt;code class=&quot;hljs&quot;&gt;tinkerbell&lt;/code&gt;&lt;/a&gt; barely &lt;em&gt;a month ago&lt;/em&gt;. Well, fret not. The
little VPS sure has been retired early, but its legacy has not been lost.
&lt;code class=&quot;hljs&quot;&gt;elsa&lt;/code&gt; was built upon the very same tools (Terraform, CoreOS, and Ignition) and
promise I got hooked on a month ago: that there is freedom in designing a
system whose &lt;em&gt;destruction&lt;/em&gt; is a non-event. If anything, working on &lt;code class=&quot;hljs&quot;&gt;elsa&lt;/code&gt; has
only &lt;em&gt;deepened&lt;/em&gt; my understanding—and my appreciation—of what Fedora has been
pushing for with CoreOS.&lt;/p&gt;
&lt;p&gt;I really, &lt;em&gt;really&lt;/em&gt; want to publish an article about &lt;code class=&quot;hljs&quot;&gt;elsa&lt;/code&gt;. I yet have to find
my angle, though. Funny story, the first couple of paragraphs of this section
were initially intended to be its introduction, until I grew dissatisfied with
them and decided to repurpose them. Which means I need to restart from scratch.
😆&lt;/p&gt;
&lt;p&gt;Anyway, as I was making good progress on &lt;code class=&quot;hljs&quot;&gt;elsa&lt;/code&gt;, I ran into some good
opportunities to contribute to some open source software. I opened two bugfix
PRs over the course of my journey: one for the &lt;a href=&quot;https://github.com/BetterStackHQ/terraform-provider-better-uptime/pull/178&quot; class=&quot;hover-rose&quot; marked=&quot;&quot;&gt;official BetterStack’s
Terraform provider&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;, and the other for &lt;a href=&quot;https://github.com/vultr/external-dns-vultr-webhook/pull/10&quot; class=&quot;hover-mint&quot; marked=&quot;&quot;&gt;external-dns’ Vultr webhook
sidecar&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;. That was pretty cool! I want to do that more often.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;We are almost a third into March already, and the month is clearly following
the trend set by its predecessor. As such, I expect to have plenty to write
about in a month’s time, meaning I should be able to keep up with publishing
these retrospective pieces for a while. At least, I hope so!&lt;/p&gt;
        
      </description>
    </item>
    
    
    
    <item>
      <title>What Happened in January 2026?</title>
      <link>https://soap.coffee/~lthms/posts/january-2026.html</link>
      <guid>https://soap.coffee/~lthms/posts/january-2026.html</guid>
      <pubDate>January 30, 2026</pubDate>
      <description>
        
        &lt;h1&gt;What Happened in January 2026?&lt;/h1&gt;&lt;div id=&quot;tags-list&quot;&gt;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#tag&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&amp;nbsp;&lt;a href=&quot;/~lthms/tags/meta.html&quot; class=&quot;tag hover-sky&quot; marked=&quot;&quot;&gt;meta&lt;/a&gt; &lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#tag&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&amp;nbsp;&lt;a href=&quot;/~lthms/tags/vibecoding.html&quot; class=&quot;tag hover-lavender&quot; marked=&quot;&quot;&gt;vibecoding&lt;/a&gt; &lt;/div&gt;
&lt;p&gt;This is my first retrospective in quite a while—I have yet to make writing
these logs a real habit of mine. That being the case, so much happened this
month that this article felt like the obvious thing to do.&lt;/p&gt;
&lt;h2&gt;&lt;code class=&quot;hljs&quot;&gt;tinkerbell&lt;/code&gt;’s First Month&lt;/h2&gt;
&lt;p&gt;On January 5th, I published &lt;a href=&quot;/~lthms/posts/i-cannot-ssh-into-my-server-anymore.html&quot; class=&quot;hover-rose&quot; marked=&quot;&quot;&gt;my account of migrating my website to a completely
new setup&lt;/a&gt;. Not only am I very
proud of this article in and of itself, I am also quite happy that it has
sparked discussions in a few places such as &lt;a href=&quot;https://lobste.rs/s/e7lpyy/i_cannot_ssh_into_my_server_anymore_s_fine&quot; class=&quot;hover-coral&quot; marked=&quot;&quot;&gt;lobste.rs&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; or the &lt;a href=&quot;https://news.ycombinator.com/item?id=46524390&quot; class=&quot;hover-mint&quot; marked=&quot;&quot;&gt;orange
website&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;. It has also been shared in other places like the &lt;a href=&quot;https://buttondown.com/devopsish/archive/devopsish-293/&quot; class=&quot;hover-periwinkle&quot; marked=&quot;&quot;&gt;DevOps’ish
newsletter&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;&lt;label for=&quot;fn1&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn1&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-right sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;I have also created a &lt;a href=&quot;https://github.com/lthms/tinkerbell&quot; class=&quot;hover-coral&quot; marked=&quot;&quot;&gt;GitHub repository&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; to host &lt;code class=&quot;hljs&quot;&gt;tinkerbell&lt;/code&gt;
configuration, and it is quietly farming stars as we speak. &lt;/span&gt;
&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;I don’t write articles with the expectation that they reach a large audience,
but I will admit I had &lt;em&gt;some&lt;/em&gt; ambitions for this one. It’s very fulfilling to
know my experiment report of sorts has caught the attention of my peers, if
only a little.&lt;/p&gt;
&lt;p&gt;At first, I thought I would spend January experimenting with my new playground.
I have a handful of services I want to deploy. Little did I know I would be
sidetracked sharply before I had a chance to get to it.&lt;/p&gt;
&lt;h2&gt;Meeting with Claude Code&lt;/h2&gt;
&lt;p&gt;Somehow, I have managed to go through 2025 while staying afar from the code
agent hype. I gave “vibecoding” a try in May by &lt;a href=&quot;/~lthms/posts/PeerProgrammingWithLLMs.html&quot; class=&quot;hover-peach&quot; marked=&quot;&quot;&gt;chatting with ChatGPT and
Gemini while yak shaving my way towards transcribing YouTube videos in
OCaml&lt;/a&gt;. Even I realized back then that the hype had already moved on from
chat UI towards agents.&lt;/p&gt;
&lt;p&gt;Then, the craziest thing happened. I went on holidays on December 23rd, 2025. Two
weeks later, as I was going back to &lt;code class=&quot;hljs language-bash&quot;&gt;&lt;span class=&quot;hljs-variable&quot;&gt;$WORK&lt;/span&gt;&lt;/code&gt;, Claude Code and its ilk
suddenly felt inevitable. It looks like agentic workflows got very good in a
matter of a few weeks, up to a point where there is a real opportunity cost in
ignoring them altogether. And as I am about to take out a mortgage, it feels
like a bad time to take the risk of falling out of relevance.&lt;/p&gt;
&lt;p&gt;Looking back, I think it started with a simple, genuine suggestion—we were
discussing a fairly ambitious clean-up of our test-suite at &lt;code class=&quot;hljs language-bash&quot;&gt;&lt;span class=&quot;hljs-variable&quot;&gt;$WORK&lt;/span&gt;&lt;/code&gt; when
someone mentioned Claude Code was quite relevant for this kind of tedious,
boilerplate-heavy task. This resonated with all the success stories I was
suddenly exposed to. Before I could realize what was happening, I was caught up
in &lt;a href=&quot;/~lthms/posts/how-i-want-to-use-llms-in-2026.html&quot; class=&quot;hover-lavender&quot; marked=&quot;&quot;&gt;an intense introspective journey&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Part of my answer to the angst of LLMs being on the verge of reshaping the way
I work is &lt;a href=&quot;https://lthms.github.io/cece/&quot; class=&quot;hover-periwinkle&quot; marked=&quot;&quot;&gt;CeCe&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;&lt;label for=&quot;fn2&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn2&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-left sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;CeCe being a nickname for Claude Code (CC). &lt;/span&gt;
&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;CeCe is a Claude Code plugin &lt;s&gt;I have been working&lt;/s&gt; Claude Code and I have
been working on for the better part of the month, and it has become my sandbox
to experiment with and understand agentic workflows. Configuring Claude Code in
a very opinionated way felt only natural—that’s the only starting point I know.
I am glad I did that, it enabled me to get a better understanding of how Claude
Code actually works.&lt;/p&gt;
&lt;p&gt;Ironically, since CeCe has mostly been written by CeCe itself and because it
grew quite fast as a result, I do not really feel confident in modifying it
myself 😆. I am now trying to &lt;a href=&quot;https://github.com/lthms/vee&quot; class=&quot;hover-lemon&quot; marked=&quot;&quot;&gt;rewrite it&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; on my
own, with more structure and “intention.” We will see how it goes.&lt;/p&gt;
&lt;h2&gt;New RSS Feeds&lt;/h2&gt;
&lt;p&gt;On a final note, I am glad to say that this website now features some new RSS
feeds that you may be interested in&lt;label for=&quot;fn3&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn3&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-right sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;That’s actually the very first task I entrusted to Claude Code.
Suffice it to say, it didn&apos;t blink, and navigated through my quite unusual
setup like a champ. &lt;/span&gt;
&lt;/span&gt;: one per &lt;a href=&quot;/~lthms/tags&quot; class=&quot;hover-peach&quot; marked=&quot;&quot;&gt;tags&lt;/a&gt;, and one per
&lt;a href=&quot;/~lthms/series&quot; class=&quot;hover-periwinkle&quot; marked=&quot;&quot;&gt;series&lt;/a&gt;. I have been meaning to implement them since I’ve added my website to
a certain aggregator that now advertises every article I publish that cites one
particular programming language.&lt;/p&gt;
&lt;p&gt;I am glad to know that camel aficionados learnt about my &lt;code class=&quot;hljs&quot;&gt;tinkerbell&lt;/code&gt; setup
simply because I drew a parallel between infrastructure as code and functional
programming. Maybe they don’t share my enthusiasm, though. With this change, I
am one PR away from limiting this aggregator’s scope to simply the articles
featuring the appropriate tag. I think they will find it useful. Maybe you will
too!&lt;/p&gt;
        
      </description>
    </item>
    
    
    
    <item>
      <title>How I Want to Use LLMs in 2026</title>
      <link>https://soap.coffee/~lthms/posts/how-i-want-to-use-llms-in-2026.html</link>
      <guid>https://soap.coffee/~lthms/posts/how-i-want-to-use-llms-in-2026.html</guid>
      <pubDate>January 25, 2026</pubDate>
      <description>
        
        &lt;h1&gt;How I Want to Use LLMs in 2026&lt;/h1&gt;&lt;div id=&quot;tags-list&quot;&gt;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#tag&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&amp;nbsp;&lt;a href=&quot;/~lthms/tags/opinions.html&quot; class=&quot;tag hover-rose&quot; marked=&quot;&quot;&gt;opinions&lt;/a&gt; &lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#tag&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&amp;nbsp;&lt;a href=&quot;/~lthms/tags/vibecoding.html&quot; class=&quot;tag hover-lemon&quot; marked=&quot;&quot;&gt;vibecoding&lt;/a&gt; &lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;I would like to thank Xavier Van de Woestyne for his feedback and careful
review.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Agentic tools are here, and they are here to stay. I don’t think it is an
overstatement to say that LLMs are completely reshaping our day-to-day life.
Even if mass adoption has yet to happen&lt;label for=&quot;fn1&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn1&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-right sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;We are seeing more and more public statements from key figures of
our industry like &lt;a href=&quot;https://www.tomshardware.com/tech-industry/artificial-intelligence/microsoft-ceo-says-ai-needs-to-have-a-wider-impact-or-else-it-risks-quickly-losing-social-permission-also-says-that-the-technology-should-benefit-more-people-to-avoid-a-bubble&quot; class=&quot;hover-periwinkle&quot; marked=&quot;&quot;&gt;Satya Nadella&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; or &lt;a href=&quot;https://www.businessinsider.com/nvidia-jensen-huang-ai-doomerism-damage-investments-2026-1&quot; class=&quot;hover-sky&quot; marked=&quot;&quot;&gt;Jensen Huang&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; trying to shed
positive light on AI and pushing for more people to embrace it—probably
because they don’t see the increase in active users they were hoping for. &lt;/span&gt;
&lt;/span&gt;, the consequences are
already here. In the software engineering industry, I already witness how they
come with expectations whether we want to use them or not.&lt;/p&gt;
&lt;p&gt;In 2025, I stayed away from the agentic hype. This month, I was made acutely
aware of how transformative they can be. I don’t think there is a way back from
there.&lt;/p&gt;
&lt;p&gt;As we jump into 2026 headfirst, I consequently find myself at a crossroads. I
will integrate LLMs in my workflow so that I can get the most out of what they
can offer, but I want to do so consciously. Hence this article, whose tone and
underlying motivation are different from my other pieces. I want to set a bar,
to form a contract of sorts between me and my future self.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;I want to be &lt;strong&gt;transparent&lt;/strong&gt; about who—or what—produced the work I am exposing
to others. I am an engineer;&amp;nbsp;I can expect to generate tons of code and
technical documentation using (meticulously prompted) agents. I am also
publishing content online, like on this very website. The last thing I want is
to trick people into thinking that I wrote something that was actually
generated by a tool. Or, even worse, that they end up convinced something I
genuinely wrote “the old way” has been generated.&lt;/p&gt;
&lt;p&gt;This does not mean generated content is without value. Nor do I think there is
a clear, objective line to draw between what are actually two ends of a
spectrum. After all, I’ve been using ChatGPT to polish my articles for a year
now, and I haven’t advertised it in the past. Still, agentic tools have become
good enough: I will be in a position to describe complex tasks, fully delegate
their implementation, and be confident enough to publish the result. I believe
it is fair for my fellow humans to be aware of that fact when they read or
review the result. Only then will they be able to calibrate their own
expectations in light of this information.&lt;/p&gt;
&lt;p&gt;As a concrete example, I have started to set up &lt;a href=&quot;https://github.com/cece-lthms&quot; class=&quot;hover-periwinkle&quot; marked=&quot;&quot;&gt;dedicated accounts for Claude
Code&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;. I am trying to come up with a reliable way to let it take over the
execution of well-scoped tasks, up to responding to reviewers’ feedback on its
own. That’s not an end I want to pursue unless I can be transparent about it.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;I want to be &lt;strong&gt;deliberate&lt;/strong&gt; about when I use or don’t use LLMs. And I have a
surprising number of reasons why.&lt;/p&gt;
&lt;p&gt;On a personal level, there are skills I don’t want to lose, craft I still wish
to improve. I’m fine with never having to write a bug report directly again,
but do I want to forgo authorship of my blog’s articles, for instance? Clearly
not.&lt;/p&gt;
&lt;p&gt;Besides, we are still grimly on track when it comes to climate change. Even
after accepting that individual behaviors have a much less impact than what
we’d like, is it really reasonable to have hours of back-and-forth with an
agent every day from now on? A lot has been said and written about the impact
of LLMs in that regard. I am under the impression that we recently read
stories that are tragically similar to what was published about Bitcoin a few
years back&lt;label for=&quot;fn2&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn2&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-left sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;I had a risk management training once, and something the instructor
said stuck with me: when something becomes &lt;em&gt;safer&lt;/em&gt;, we humans tend to adapt
our behavior to take more risks. Are we doing the same thing with climate
change? When we manage to reduce our environmental impact, do we
collectively interpret that news as a blank check to find new ways to
consume more energy? &lt;/span&gt;
&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;I think one answer to this is to refuse to make the LLM the default, obvious
choice. Using an agent should have weight to it. There will be times when an
agent will be an enabler—achieving something outside of my immediate
reach&lt;label for=&quot;fn3&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn3&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-right sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;Because it would take me too much time, because it would be extremely
tedious and error-prone, or for an infinite number of valid reasons. &lt;/span&gt;
&lt;/span&gt;. And there will be times when I will want to use it to write a
trivial patch that I could come up with myself in a matter of minutes. I want
to cultivate the discipline to make the distinction between the former and the
latter, so that I avoid falling into wasteful habits.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;I want to be &lt;strong&gt;respectful&lt;/strong&gt; of the people who will be confronted with my use of
LLMs. The matter is too complicated to approach any other way. What is
acceptable for some feels like an attack on others.&lt;/p&gt;
&lt;p&gt;Yes, agents can be powerful accelerators. No, adopting them is neither easy nor
the obvious, right thing to do. We are seeing too many people being hurt by the
behaviors enabled by agents’ capabilities: open-source maintainers closing
their projects from external contributions, junior engineers struggling like
never before to find jobs, artists witnessing in real time their art being
regurgitated by models trained on their portfolios. The list goes on.&lt;/p&gt;
&lt;p&gt;I cannot commit to never using generated art, or to close my eyes to what LLMs
can bring me. But I want to be &lt;em&gt;aware&lt;/em&gt;—and to &lt;em&gt;care&lt;/em&gt;—about the consequences of
my choices, as well as the broader context behind them. And sometimes, that
needs to mean renouncing the convenience that a technology can bring, even if
for just a moment.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Only time will tell how I end up using LLMs, and whether the principles I claim
today as my own in this article will stand the test of time. I am rather
curious to reread this piece in a year—I hope it will prompt (ah!) me to write
a retrospective.&lt;/p&gt;
        
      </description>
    </item>
    
    
    
    <item>
      <title>Peer-Programming in Modern OCaml with ChatGPT and Gemini</title>
      <link>https://soap.coffee/~lthms/posts/PeerProgrammingWithLLMs.html</link>
      <guid>https://soap.coffee/~lthms/posts/PeerProgrammingWithLLMs.html</guid>
      <pubDate>June 2, 2025</pubDate>
      <description>
        
        &lt;h1&gt;Peer-Programming in Modern OCaml with ChatGPT and Gemini&lt;/h1&gt;&lt;div id=&quot;tags-list&quot;&gt;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#tag&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&amp;nbsp;&lt;a href=&quot;/~lthms/tags/ocaml.html&quot; class=&quot;tag hover-lemon&quot; marked=&quot;&quot;&gt;ocaml&lt;/a&gt; &lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#tag&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&amp;nbsp;&lt;a href=&quot;/~lthms/tags/vibecoding.html&quot; class=&quot;tag hover-lavender&quot; marked=&quot;&quot;&gt;vibecoding&lt;/a&gt; &lt;/div&gt;
&lt;p&gt;It is June 2025, and LLMs are everywhere and do everything now. I have never
been a diligent adopter of them myself. The past few months, I started to feel
a bit “left out,” though. Colleagues and friends are starting to integrate
LLM-powered tools into their personal toolkit, with notable successes.&lt;/p&gt;
&lt;p&gt;Early May, I decided to challenge myself to implement a simple tool to generate
a summary from YouTube videos using &lt;a href=&quot;https://alphacephei.com/vosk/&quot; class=&quot;hover-lemon&quot; marked=&quot;&quot;&gt;Vosk&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; for
speech recognition and &lt;a href=&quot;https://ollama.com/&quot; class=&quot;hover-periwinkle&quot; marked=&quot;&quot;&gt;Ollama&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; for generating summaries
using LLMs running locally. I could hit two birds with one stone—experimenting
with LLMs to write and power software.&lt;/p&gt;
&lt;p&gt;I decided to implement as much as possible in OCaml, for two main reasons.
Firstly, this is the main language I use at &lt;code class=&quot;hljs language-bash&quot;&gt;&lt;span class=&quot;hljs-variable&quot;&gt;$WORK&lt;/span&gt;&lt;/code&gt;. I wanted to get a
sense of how LLMs could help with the software stack I used 7+ hours a day.
Secondly, it was a good opportunity to catch-up with the OCaml 5 ecosystem
(&lt;a href=&quot;https://github.com/ocaml-multicore/eio&quot; class=&quot;hover-rose&quot; marked=&quot;&quot;&gt;Eio&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; in particular).&lt;/p&gt;
&lt;p&gt;This write-up is a sort of dev log of this exercise. Its main focus is not to
explain in depth the code I ended up writing, but rather to recollect on my
wins and losses in adding LLMs in my developer toolkit.&lt;/p&gt;
&lt;h2&gt;TL;DR&lt;/h2&gt;
&lt;p&gt;In this article, I am using “Tip” blocks to highlight my key findings and
lessons learned. That being said, for readers in a hurry, here’s how ChatGPT
summarizes these blocks.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Prompting is a skill that improves through trial and error—many failed
prompts help build intuition.&lt;/li&gt;
&lt;li&gt;LLMs may suggest non-existent functions; using LSP tools helps identify these
quickly.&lt;/li&gt;
&lt;li&gt;Standard formats like WAV lead to more accurate LLM outputs.&lt;/li&gt;
&lt;li&gt;LLMs without session memory tend to repeat mistakes; shared context is
important.&lt;/li&gt;
&lt;li&gt;Structuring commit message prompts (e.g., What / Why / How) produces
consistently good results.&lt;/li&gt;
&lt;li&gt;LLMs struggle with libraries like Eio, possibly due to name ambiguity or
unstable APIs.&lt;/li&gt;
&lt;li&gt;Providing project-specific context (e.g., via &lt;code class=&quot;hljs&quot;&gt;direnv&lt;/code&gt;) is likely to help
reduce repeated hallucinations.&lt;/li&gt;
&lt;li&gt;Prompting LLMs for MR descriptions or commits can eliminate empty submissions
and speed up review.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You should still definitely read the full piece, though. I don’t think my
prompt was particularly good 🤫.&lt;/p&gt;
&lt;h2&gt;Editor Integration&lt;/h2&gt;
&lt;p&gt;My first task was to grant myself the ability to leverage LLMs from my editor.
I had been using the web chat of ChatGPT for a while, but it now felt
antiquated since I had seen a freshly hired coworker get ChatGPT to generate
for themselves a dozen tests directly from VS Code.&lt;/p&gt;
&lt;p&gt;I have returned to &lt;a href=&quot;https://neovim.io&quot; class=&quot;hover-periwinkle&quot; marked=&quot;&quot;&gt;Neovim&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; for a few years, and I am not
ready to migrate to VS Code. I would have been surprised if the Vim/Neovim
communities wouldn’t have a viable plugin for me, though.&lt;/p&gt;
&lt;p&gt;I asked both ChatGPT and Gemini to find my candidates, but the plugins they
suggested seemed unmaintained, often outdated.&lt;/p&gt;
&lt;p&gt;In the end, I found &lt;a href=&quot;https://github.com/olimorris/codecompanion.nvim&quot; class=&quot;hover-lemon&quot; marked=&quot;&quot;&gt;CodeCompanion.nvim&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;
by myself, through a good old Google research. I asked ChatGPT why it hadn’t
suggested it to me, and it seems like my prompt were biased. By asking for
“a Neovim ChatGPT plugin” or “a plugin to integrate Gemini to Neovim,” I had
unnecessarily narrowed the LLM scope.&lt;/p&gt;
&lt;div class=&quot;markdown-alert markdown-alert-tip&quot;&gt;&lt;p class=&quot;markdown-alert-title&quot;&gt;&lt;svg class=&quot;octicon octicon-light-bulb mr-2&quot; viewbox=&quot;0 0 16 16&quot; version=&quot;1.1&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M8 1.5c-2.363 0-4 1.69-4 3.75 0 .984.424 1.625.984 2.304l.214.253c.223.264.47.556.673.848.284.411.537.896.621 1.49a.75.75 0 0 1-1.484.211c-.04-.282-.163-.547-.37-.847a8.456 8.456 0 0 0-.542-.68c-.084-.1-.173-.205-.268-.32C3.201 7.75 2.5 6.766 2.5 5.25 2.5 2.31 4.863 0 8 0s5.5 2.31 5.5 5.25c0 1.516-.701 2.5-1.328 3.259-.095.115-.184.22-.268.319-.207.245-.383.453-.541.681-.208.3-.33.565-.37.847a.751.751 0 0 1-1.485-.212c.084-.593.337-1.078.621-1.489.203-.292.45-.584.673-.848.075-.088.147-.173.213-.253.561-.679.985-1.32.985-2.304 0-2.06-1.637-3.75-4-3.75ZM5.75 12h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1 0-1.5ZM6 15.25a.75.75 0 0 1 .75-.75h2.5a.75.75 0 0 1 0 1.5h-2.5a.75.75 0 0 1-.75-.75Z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;Tip&lt;/p&gt;&lt;p&gt;I guess one does not become a prompt engineer in a day. This is actually one
of the reasons I want to use LLMs more seriously. To build myself intuitions
of which prompts work and which don’t. After this project, I have mostly
uncovered a bunch of the latter category 😅.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;https://twitter.com/yurug&quot; class=&quot;hover-coral&quot; marked=&quot;&quot;&gt;&lt;strong&gt;@yurug&lt;/strong&gt;&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; had told me he was impressed by Gemini
Pro, so I decided to make it the default adapter for the &lt;code class=&quot;hljs&quot;&gt;CodeCompanionChat&lt;/code&gt;
command. I tried to make Gemini Pro the default model for this adapter, it was
challenging and LLMs weren’t able to help. When I finally found the correct
&lt;code class=&quot;hljs&quot;&gt;setup&lt;/code&gt; option, it turns out I hadn’t generated a token allowing me to use Pro.&lt;/p&gt;
&lt;p&gt;Well. That gave me the opportunity to benchmark Gemini Flash, then.&lt;/p&gt;
&lt;h2&gt;Speech Recognition with Vosk&lt;/h2&gt;
&lt;p&gt;ChatGPT suggested Vosk as a way to get a transcript of an audio file, so it was
also a good opportunity to write bindings (something I had dodged for a long
time for no particular reason).&lt;/p&gt;
&lt;p&gt;As of June 2025, there is no OCaml bindings for the &lt;a href=&quot;https://github.com/alphacep/vosk-api/blob/master/src/vosk_api.h&quot; class=&quot;hover-lemon&quot; marked=&quot;&quot;&gt;Vosk
API&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;, so my
first task was to write my own as part of a project soberly called
&lt;a href=&quot;https://github.com/lthms/ocaml-vosk&quot; class=&quot;hover-sky&quot; marked=&quot;&quot;&gt;&lt;code class=&quot;hljs&quot;&gt;ocaml-vosk&lt;/code&gt;&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Gemini Flash was able to help me understand how &lt;code class=&quot;hljs&quot;&gt;ctypes&lt;/code&gt; and &lt;code class=&quot;hljs&quot;&gt;ctypes.foreign&lt;/code&gt;
works. This was my first experience interacting with an LLM from my Neovim
window, and it was pretty convincing. It gave me the opportunity to learn that
one can declare opaque types in OCaml (not just via mli files). It makes sense,
but it was news to me.&lt;/p&gt;
&lt;p&gt;Then, Gemini suggested me to use &lt;a href=&quot;https://ocaml-multicore.github.io/eio/eio/Eio/Switch/&quot; class=&quot;hover-lemon&quot; marked=&quot;&quot;&gt;EIO’s
&lt;code class=&quot;hljs language-ocaml&quot;&gt;&lt;span class=&quot;hljs-type&quot;&gt;Switch&lt;/span&gt;&lt;/code&gt;&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;
to deal with automatic memory management (in place of &lt;code class=&quot;hljs&quot;&gt;Gc.finalise&lt;/code&gt;). It was
the first time I heard about it, and the fact that I learned their existence
from the perspective of resource management (not fiber management) was a good
accident.&lt;/p&gt;
&lt;p&gt;The first point of friction came when I started build a high-level interface
for my Vosk bindings. More specifically, given a
&lt;a href=&quot;https://ocaml.org/p/cstruct/latest&quot; class=&quot;hover-lavender&quot; marked=&quot;&quot;&gt;&lt;code class=&quot;hljs&quot;&gt;Cstruct.t&lt;/code&gt;&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; value, how do I get a pointer
and a length? It turns out that while both ChatGPT and Gemini Pro know how to
do so, Gemini Flash hallucinates every step of the way.&lt;/p&gt;
&lt;p&gt;The solution is actually pretty straightforward.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-ocaml&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; ptr =
  &lt;span class=&quot;hljs-type&quot;&gt;Ctypes&lt;/span&gt;.bigarray_start
    &lt;span class=&quot;hljs-type&quot;&gt;Ctypes&lt;/span&gt;.array1
    (&lt;span class=&quot;hljs-type&quot;&gt;Cstruct&lt;/span&gt;.to_bigarray buffer)
&lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; len = buffer.&lt;span class=&quot;hljs-type&quot;&gt;Cstruct&lt;/span&gt;.len &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Gemini Flash kept suggesting I use &lt;code class=&quot;hljs&quot;&gt;Ctypes.ptr_add&lt;/code&gt; instead, though. Don’t
search for it, it does not exist&lt;label for=&quot;fn1&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn1&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-right sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;While reviewing this article, ChatGPT gently hinted that while
&lt;code class=&quot;hljs&quot;&gt;ptr_add&lt;/code&gt; does not exist, &lt;code class=&quot;hljs language-ocaml&quot;&gt;&lt;span class=&quot;hljs-type&quot;&gt;Ctypes&lt;/span&gt;.(+@)&lt;/code&gt; does. &lt;/span&gt;
&lt;/span&gt;. When I suggested &lt;code class=&quot;hljs&quot;&gt;Cstruct.to_bigarray&lt;/code&gt;,
it warned me about the fact that this call would create a copy of the
underlying buffer. ChatGPT and Gemini Pro disagreed, and I could convince
myself that they were right by looking at the code. Interestingly, I was also
able to convince Gemini Flash it was wrong by copy/pasting the relevant code
snippet.&lt;/p&gt;
&lt;div class=&quot;markdown-alert markdown-alert-tip&quot;&gt;&lt;p class=&quot;markdown-alert-title&quot;&gt;&lt;svg class=&quot;octicon octicon-light-bulb mr-2&quot; viewbox=&quot;0 0 16 16&quot; version=&quot;1.1&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M8 1.5c-2.363 0-4 1.69-4 3.75 0 .984.424 1.625.984 2.304l.214.253c.223.264.47.556.673.848.284.411.537.896.621 1.49a.75.75 0 0 1-1.484.211c-.04-.282-.163-.547-.37-.847a8.456 8.456 0 0 0-.542-.68c-.084-.1-.173-.205-.268-.32C3.201 7.75 2.5 6.766 2.5 5.25 2.5 2.31 4.863 0 8 0s5.5 2.31 5.5 5.25c0 1.516-.701 2.5-1.328 3.259-.095.115-.184.22-.268.319-.207.245-.383.453-.541.681-.208.3-.33.565-.37.847a.751.751 0 0 1-1.485-.212c.084-.593.337-1.078.621-1.489.203-.292.45-.584.673-.848.075-.088.147-.173.213-.253.561-.679.985-1.32.985-2.304 0-2.06-1.637-3.75-4-3.75ZM5.75 12h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1 0-1.5ZM6 15.25a.75.75 0 0 1 .75-.75h2.5a.75.75 0 0 1 0 1.5h-2.5a.75.75 0 0 1-.75-.75Z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;Tip&lt;/p&gt;&lt;p&gt;Having an LLM suggesting you to use a function which does not exist is &lt;em&gt;very&lt;/em&gt;
frustrating. Especially if it happens several times in a row—it recognizes
its mistake and proposes an alternative that is as nonexistant as the first
one.&amp;nbsp;At least, with LSP it is pretty straightforward to know when it happens.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Using Vosk is one thing, but then I couldn’t find any OCaml package to read
audio files compatible with Vosk expectations. Implementing what I needed in
OCaml gave me more opportunities to learn about EIO, but most importantly, it
showed how having a chat with an LLM directly from my editor was convenient. I
was able to learn about WAV files, RIFF header and subchunks and PCB 16-bit
mono audio data without leaving Neovim. And by giving Gemini access to my
buffer, I troubleshot most of my issues fairly quickly (except when they were
EIO-specific—more on that later).&lt;/p&gt;
&lt;div class=&quot;markdown-alert markdown-alert-tip&quot;&gt;&lt;p class=&quot;markdown-alert-title&quot;&gt;&lt;svg class=&quot;octicon octicon-light-bulb mr-2&quot; viewbox=&quot;0 0 16 16&quot; version=&quot;1.1&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M8 1.5c-2.363 0-4 1.69-4 3.75 0 .984.424 1.625.984 2.304l.214.253c.223.264.47.556.673.848.284.411.537.896.621 1.49a.75.75 0 0 1-1.484.211c-.04-.282-.163-.547-.37-.847a8.456 8.456 0 0 0-.542-.68c-.084-.1-.173-.205-.268-.32C3.201 7.75 2.5 6.766 2.5 5.25 2.5 2.31 4.863 0 8 0s5.5 2.31 5.5 5.25c0 1.516-.701 2.5-1.328 3.259-.095.115-.184.22-.268.319-.207.245-.383.453-.541.681-.208.3-.33.565-.37.847a.751.751 0 0 1-1.485-.212c.084-.593.337-1.078.621-1.489.203-.292.45-.584.673-.848.075-.088.147-.173.213-.253.561-.679.985-1.32.985-2.304 0-2.06-1.637-3.75-4-3.75ZM5.75 12h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1 0-1.5ZM6 15.25a.75.75 0 0 1 .75-.75h2.5a.75.75 0 0 1 0 1.5h-2.5a.75.75 0 0 1-.75-.75Z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;Tip&lt;/p&gt;&lt;p&gt;For widespread encoding like WAV files, LLMs shine particularly bright.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;In the end, EIO-specific code put aside, this task was roughly solved by (1)
writing bindings for the few functions of the Vosk API I needed, and (2)
translating C examples provided by Gemini into good-looking OCaml&lt;label for=&quot;fn2&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn2&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-left sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;It’s a little out of scope for this article, but I discovered when
writing the high-level API for Vosk that &lt;code class=&quot;hljs language-ocaml&quot;&gt;&lt;span class=&quot;hljs-type&quot;&gt;Switch&lt;/span&gt;&lt;/code&gt;es are very easy
to misuse. It is as simple as (incorrectly) turning an eager function
consuming a buffer into a &lt;code class=&quot;hljs&quot;&gt;Seq&lt;/code&gt;-based alternative, while forgetting the use
of &lt;code class=&quot;hljs&quot;&gt;Switch.run&lt;/code&gt; on top of the function. &lt;/span&gt;
&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Witnessing my example program outputting the transcript of audio files as it
was processing them felt pretty good, and I was soon ready to tackle the second
part of this project: prompting a LLM to summarize it.&lt;/p&gt;
&lt;h2&gt;Prompting Local LLMs with Ollama&lt;/h2&gt;
&lt;p&gt;Similarly to Vosk, there is no on the shelf package available to use Ollama
from an OCaml program. As a consequence, I created a second repository
(&lt;a href=&quot;https://github.com/lthms/ocaml-ollama&quot; class=&quot;hover-peach&quot; marked=&quot;&quot;&gt;&lt;code class=&quot;hljs&quot;&gt;ocaml-ollama&lt;/code&gt;&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; if you can believe it).&lt;/p&gt;
&lt;h3&gt;How It Started&lt;/h3&gt;
&lt;p&gt;Turns out, you don’t use Ollama the same way you use Vosk. The latter is a C
library that you can call from your binary, the former actually uses a
client/server architecture. I asked LLMs what was the best solution for
performing HTTP requests with Eio, and &lt;code class=&quot;hljs&quot;&gt;cohttp-eio&lt;/code&gt; came back as a good
candidate. I’m already familiar with &lt;code class=&quot;hljs&quot;&gt;cohttp&lt;/code&gt;, since we are using it at
&lt;code class=&quot;hljs language-bash&quot;&gt;&lt;span class=&quot;hljs-variable&quot;&gt;$WORK&lt;/span&gt;&lt;/code&gt;, but it’s actually a transitive dependency (of a framework
called &lt;a href=&quot;https://ocaml.org/p/resto/latest&quot; class=&quot;hover-periwinkle&quot; marked=&quot;&quot;&gt;&lt;code class=&quot;hljs&quot;&gt;resto&lt;/code&gt;&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;I am actually a little frustrated with &lt;code class=&quot;hljs&quot;&gt;resto&lt;/code&gt;, so I welcomed the opportunity
to familiar myself a little more with &lt;code class=&quot;hljs&quot;&gt;cohttp&lt;/code&gt; directly. I quickly implemented
the helper fetching the list of models available from a given Ollama instance.&lt;/p&gt;
&lt;p&gt;Then, I got myself side tracked.&lt;/p&gt;
&lt;h3&gt;More LLMs Lies&lt;/h3&gt;
&lt;p&gt;Persistent HTTP connections are a pet peeve of mine. Establishing a TCP
connection, negotiating TLS encryption, all of that takes time—creating a new
socket for each request a daemon really frustrates me as a result.&lt;/p&gt;
&lt;p&gt;So I asked.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Does &lt;code class=&quot;hljs&quot;&gt;cohttp-eio&lt;/code&gt; reuses already established connections when performing two
requests on the same host?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;ChatGPT 4o. Gemini 2.5 Flash. Gemini 2.5 Pro. They all assured me it was the
case, as long as I was careful and reused the same
&lt;code class=&quot;hljs language-ocaml&quot;&gt;&lt;span class=&quot;hljs-type&quot;&gt;Cohttp_eio&lt;/span&gt;.&lt;span class=&quot;hljs-type&quot;&gt;Client&lt;/span&gt;.t&lt;/code&gt; instance. For instance, here is the first few
words of ChatGPT when prompted with this question.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;As of current behavior in &lt;code class=&quot;hljs&quot;&gt;cohttp-eio-client&lt;/code&gt;, &lt;strong&gt;yes&lt;/strong&gt;, it does &lt;strong&gt;reuse
already established connections&lt;/strong&gt; when making multiple requests to the same
host, provided certain conditions are met.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It’s a lie. Don’t trust them. They don’t reuse existing HTTP connection.&lt;/p&gt;
&lt;p&gt;I was very doubtful, so I asked them how to check this. &lt;code class=&quot;hljs&quot;&gt;tcpdump&lt;/code&gt; was
mentioned&lt;label for=&quot;fn3&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn3&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-right sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;I later discovered &lt;a href=&quot;https://github.com/ocaml-multicore/eio-trace&quot; class=&quot;hover-rose&quot; marked=&quot;&quot;&gt;&lt;code class=&quot;hljs&quot;&gt;eio-trace&lt;/code&gt;&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;
and it would have been much more straightforward to use this tool to
inspect &lt;code class=&quot;hljs language-ocaml&quot;&gt;&lt;span class=&quot;hljs-type&quot;&gt;Cohttp_eio&lt;/span&gt;.&lt;span class=&quot;hljs-type&quot;&gt;Client&lt;/span&gt;&lt;/code&gt;’s default behavior. No LLM thought of
that, sadly. &lt;/span&gt;
&lt;/span&gt;. I got traces I couldn’t read at first glance, so I just
copy/pasted them to the LLMs… and sure enough, they confirmed what I suspected.
&lt;code class=&quot;hljs language-ocaml&quot;&gt;&lt;span class=&quot;hljs-type&quot;&gt;Cohttp_eio&lt;/span&gt;.&lt;span class=&quot;hljs-type&quot;&gt;Client&lt;/span&gt;&lt;/code&gt; does not share connections by default. It creates a
socket for each request.&lt;/p&gt;
&lt;p&gt;It’s actually pretty easy to convince yourself that it is the case by reading
the implementation of
&lt;a href=&quot;https://github.com/mirage/ocaml-cohttp/blob/main/cohttp-eio/src/client.ml#L83&quot; class=&quot;hover-lavender&quot; marked=&quot;&quot;&gt;&lt;code class=&quot;hljs language-ocaml&quot;&gt;&lt;span class=&quot;hljs-type&quot;&gt;Cohttp_eio&lt;/span&gt;.&lt;span class=&quot;hljs-type&quot;&gt;Client&lt;/span&gt;&lt;/code&gt;&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-ocaml&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;type&lt;/span&gt; connection = &lt;span class=&quot;hljs-type&quot;&gt;Eio&lt;/span&gt;.&lt;span class=&quot;hljs-type&quot;&gt;Flow&lt;/span&gt;.two_way_ty r
&lt;span class=&quot;hljs-keyword&quot;&gt;type&lt;/span&gt; t = sw:&lt;span class=&quot;hljs-type&quot;&gt;Switch&lt;/span&gt;.t -&amp;gt; &lt;span class=&quot;hljs-type&quot;&gt;Uri&lt;/span&gt;.t -&amp;gt; connection

&lt;span class=&quot;hljs-comment&quot;&gt;(* simplified version of [make], omitting the support for HTTPS *)&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; make &lt;span class=&quot;hljs-literal&quot;&gt;()&lt;/span&gt; net : t = &lt;span class=&quot;hljs-keyword&quot;&gt;fun&lt;/span&gt; ~sw uri -&amp;gt;
  (&lt;span class=&quot;hljs-type&quot;&gt;Eio&lt;/span&gt;.&lt;span class=&quot;hljs-type&quot;&gt;Net&lt;/span&gt;.connect ~sw net (unix_address uri) :&amp;gt; connection)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There is &lt;em&gt;nothing&lt;/em&gt; here dealing with persistent connections. &lt;code class=&quot;hljs&quot;&gt;Eio.Net.connect&lt;/code&gt;
uses a switch for resource management, but does not perform any kind of
connection caching.&lt;/p&gt;
&lt;p&gt;That’s okay, though. Yak shaving is a real thing. I can stop working on my
Ollama client library for a while, just to &lt;em&gt;fix this&lt;/em&gt;.&lt;/p&gt;
&lt;h3&gt;The Questionable Side Quest of Implementing a Connection Pool for &lt;code class=&quot;hljs&quot;&gt;cohttp-eio&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The bottom-line of this little adventure is: I should have updated my default
prompt to remind the LLMs that &lt;code class=&quot;hljs language-ocaml&quot;&gt;&lt;span class=&quot;hljs-type&quot;&gt;Cohttp_eio&lt;/span&gt;.&lt;span class=&quot;hljs-type&quot;&gt;Body&lt;/span&gt;.drain&lt;/code&gt; in &lt;em&gt;not&lt;/em&gt; a thing.&lt;/p&gt;
&lt;p&gt;But let’s start from the beginning. Over the course of a few days, I have
successfully implemented a wrapper on top of &lt;code class=&quot;hljs language-ocaml&quot;&gt;&lt;span class=&quot;hljs-type&quot;&gt;Cohttp_eio&lt;/span&gt;.&lt;span class=&quot;hljs-type&quot;&gt;Client&lt;/span&gt;&lt;/code&gt; to
deal with persistent connections. It’s not rocket science, but it’s still a
subtle endeavor, which necessitated a good understanding of Eio and &lt;code class=&quot;hljs&quot;&gt;cohttp&lt;/code&gt;. I
cannot say LLMs were instrumental for the task. They gave me good pointers to
start from, but they also misled me a bunch of times.&lt;/p&gt;
&lt;p&gt;Sometimes, the help came in surprising ways. One anecdote in particular
stuck with me. I decided I needed a &lt;code class=&quot;hljs language-ocaml&quot;&gt;get&lt;/code&gt; operation for
&lt;a href=&quot;https://ocaml-multicore.github.io/eio/eio/Eio/Pool/&quot; class=&quot;hover-lemon&quot; marked=&quot;&quot;&gt;&lt;code class=&quot;hljs language-ocaml&quot;&gt;&lt;span class=&quot;hljs-type&quot;&gt;Eio&lt;/span&gt;.&lt;span class=&quot;hljs-type&quot;&gt;Pool&lt;/span&gt;&lt;/code&gt;&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;
pools, which sadly only proposes &lt;code class=&quot;hljs&quot;&gt;use&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-ocaml&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;(* Provided by Eio.Pool *)&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;val&lt;/span&gt; use : &lt;span class=&quot;hljs-symbol&quot;&gt;&apos;a&lt;/span&gt; t -&amp;gt; (&lt;span class=&quot;hljs-symbol&quot;&gt;&apos;a&lt;/span&gt; -&amp;gt; &lt;span class=&quot;hljs-symbol&quot;&gt;&apos;b&lt;/span&gt;) -&amp;gt; &lt;span class=&quot;hljs-symbol&quot;&gt;&apos;b&lt;/span&gt;

&lt;span class=&quot;hljs-comment&quot;&gt;(* Not provided *)&lt;/span&gt;
&lt;span class=&quot;hljs-keyword&quot;&gt;val&lt;/span&gt; get : sw:&lt;span class=&quot;hljs-type&quot;&gt;Switch&lt;/span&gt;.t -&amp;gt; &lt;span class=&quot;hljs-symbol&quot;&gt;&apos;a&lt;/span&gt; t -&amp;gt; &lt;span class=&quot;hljs-symbol&quot;&gt;&apos;a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The key insight is that &lt;code class=&quot;hljs&quot;&gt;get&lt;/code&gt; allows callers to pick something from the pool,
and only put it back when the switch is released.&lt;/p&gt;
&lt;p&gt;My first implementation of &lt;code class=&quot;hljs&quot;&gt;get&lt;/code&gt; was roughly as follows&lt;label for=&quot;fn4&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn4&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-left sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;I didn’t even consider asking an LLM to propose me an implementation,
now that I think about it. I really am no vibe coder yet. &lt;/span&gt;
&lt;/span&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-ocaml&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;open&lt;/span&gt; &lt;span class=&quot;hljs-type&quot;&gt;Eio&lt;/span&gt;.&lt;span class=&quot;hljs-type&quot;&gt;Std&lt;/span&gt;

&lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; get ~sw t =
  &lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; x, rx = &lt;span class=&quot;hljs-type&quot;&gt;Promise&lt;/span&gt;.create &lt;span class=&quot;hljs-literal&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt;
  &lt;span class=&quot;hljs-keyword&quot;&gt;let&lt;/span&gt; never, _ = &lt;span class=&quot;hljs-type&quot;&gt;Promise&lt;/span&gt;.create &lt;span class=&quot;hljs-literal&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt;
  &lt;span class=&quot;hljs-type&quot;&gt;Fiber&lt;/span&gt;.fork ~sw (&lt;span class=&quot;hljs-keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;()&lt;/span&gt; -&amp;gt;
      &lt;span class=&quot;hljs-type&quot;&gt;Eio&lt;/span&gt;.&lt;span class=&quot;hljs-type&quot;&gt;Pool&lt;/span&gt;.use t @@ &lt;span class=&quot;hljs-keyword&quot;&gt;fun&lt;/span&gt; conn -&amp;gt;
      &lt;span class=&quot;hljs-type&quot;&gt;Promise&lt;/span&gt;.resolve rx conn;
      &lt;span class=&quot;hljs-type&quot;&gt;Promise&lt;/span&gt;.await never);
  &lt;span class=&quot;hljs-type&quot;&gt;Promise&lt;/span&gt;.await x
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And it didn’t work. The resulting program was hanging, because of how
&lt;code class=&quot;hljs language-ocaml&quot;&gt;&lt;span class=&quot;hljs-type&quot;&gt;Fiber&lt;/span&gt;.fork ~sw&lt;/code&gt; works. Basically, the fiber created by &lt;code class=&quot;hljs&quot;&gt;fork&lt;/code&gt; becomes
part of the set of fibers the switch &lt;code class=&quot;hljs&quot;&gt;sw&lt;/code&gt; waits for. Since, in my case, said
fiber would never be resolved, I had created a deadlock.&lt;/p&gt;
&lt;p&gt;I asked Gemini Pro 2.5 for help, and out of curiosity, I looked at its
reasoning steps. Very early on, it mentioned &lt;code class=&quot;hljs&quot;&gt;Fiber.fork_daemon&lt;/code&gt;, but
surprisingly &lt;code class=&quot;hljs&quot;&gt;Fiber.fork_daemon&lt;/code&gt; was not mentioned in the final answer&lt;label for=&quot;fn5&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn5&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-right sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;Once again, I had asked the wrong question. I asked for the &lt;code class=&quot;hljs&quot;&gt;Fiber&lt;/code&gt;
equivalent of &lt;code class=&quot;hljs&quot;&gt;Lwt.async&lt;/code&gt;. I had overlooked that &lt;code class=&quot;hljs&quot;&gt;Lwt.async&lt;/code&gt; had a very
particular behavior wrt. exceptions, that Gemini Pro tried very hard to
replicate. I didn’t care at all about the exceptions I could raise, here! &lt;/span&gt;
&lt;/span&gt;.
Have I not been curious at that time, I would have missed the correct
solution&lt;label for=&quot;fn6&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn6&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-left sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;&lt;a href=&quot;https://bsky.app/@welltypedwit.ch&quot; class=&quot;hover-peach&quot; marked=&quot;&quot;&gt;@alice&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#bsky&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; provided me the answer a
few minutes later, so I’d have been fine in the end 😅. &lt;/span&gt;
&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;I think my experience overall was made a little more frustrating than it should
have been because I have never constructed a “context” that I could share
between coding sessions. I haven’t enabled the memory saving setting in
ChatGPT. Besides, everytime I opened Neovim, Gemini was starting from scratch.
I should try to change that, to prevent the LLMs from doing the same mistakes
again and again—typically, the &lt;code class=&quot;hljs&quot;&gt;Cohttp_eio.Body.drain&lt;/code&gt; function they kept
bringing up.&lt;/p&gt;
&lt;div class=&quot;markdown-alert markdown-alert-tip&quot;&gt;&lt;p class=&quot;markdown-alert-title&quot;&gt;&lt;svg class=&quot;octicon octicon-light-bulb mr-2&quot; viewbox=&quot;0 0 16 16&quot; version=&quot;1.1&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M8 1.5c-2.363 0-4 1.69-4 3.75 0 .984.424 1.625.984 2.304l.214.253c.223.264.47.556.673.848.284.411.537.896.621 1.49a.75.75 0 0 1-1.484.211c-.04-.282-.163-.547-.37-.847a8.456 8.456 0 0 0-.542-.68c-.084-.1-.173-.205-.268-.32C3.201 7.75 2.5 6.766 2.5 5.25 2.5 2.31 4.863 0 8 0s5.5 2.31 5.5 5.25c0 1.516-.701 2.5-1.328 3.259-.095.115-.184.22-.268.319-.207.245-.383.453-.541.681-.208.3-.33.565-.37.847a.751.751 0 0 1-1.485-.212c.084-.593.337-1.078.621-1.489.203-.292.45-.584.673-.848.075-.088.147-.173.213-.253.561-.679.985-1.32.985-2.304 0-2.06-1.637-3.75-4-3.75ZM5.75 12h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1 0-1.5ZM6 15.25a.75.75 0 0 1 .75-.75h2.5a.75.75 0 0 1 0 1.5h-2.5a.75.75 0 0 1-.75-.75Z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;Tip&lt;/p&gt;&lt;p&gt;I need to investigate how I can specialize my default prompt for each
software project I am working on. I imagine I can rely on an environment
variable and &lt;a href=&quot;https://direnv.net/&quot; class=&quot;hover-lemon&quot; marked=&quot;&quot;&gt;&lt;code class=&quot;hljs&quot;&gt;direnv&lt;/code&gt;&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Finally, it’s when I worked on this library that I came up with a nice prompt
for Gemini to write my git commit messages for me.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code class=&quot;hljs&quot;&gt;@editor&lt;/code&gt; &lt;code class=&quot;hljs&quot;&gt;#buffer&lt;/code&gt; Add a git commit title and message. Structure the
description in three sections (What, Why, How). Wrap the sections at 72
columns. Don’t forget the git title, and always insert a new line between the
title and the description.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This prompt gives pretty cool result. It is still necessary to review it,
because in a few instances I caught false statement in the proposal. But
overall, it gives really meaningful output. Almost &lt;a href=&quot;https://github.com/lthms/cohttp-connpool-eio/commits/main/&quot; class=&quot;hover-periwinkle&quot; marked=&quot;&quot;&gt;all commits of the
library&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt; have been
written with this prompt.&lt;/p&gt;
&lt;div class=&quot;markdown-alert markdown-alert-tip&quot;&gt;&lt;p class=&quot;markdown-alert-title&quot;&gt;&lt;svg class=&quot;octicon octicon-light-bulb mr-2&quot; viewbox=&quot;0 0 16 16&quot; version=&quot;1.1&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M8 1.5c-2.363 0-4 1.69-4 3.75 0 .984.424 1.625.984 2.304l.214.253c.223.264.47.556.673.848.284.411.537.896.621 1.49a.75.75 0 0 1-1.484.211c-.04-.282-.163-.547-.37-.847a8.456 8.456 0 0 0-.542-.68c-.084-.1-.173-.205-.268-.32C3.201 7.75 2.5 6.766 2.5 5.25 2.5 2.31 4.863 0 8 0s5.5 2.31 5.5 5.25c0 1.516-.701 2.5-1.328 3.259-.095.115-.184.22-.268.319-.207.245-.383.453-.541.681-.208.3-.33.565-.37.847a.751.751 0 0 1-1.485-.212c.084-.593.337-1.078.621-1.489.203-.292.45-.584.673-.848.075-.088.147-.173.213-.253.561-.679.985-1.32.985-2.304 0-2.06-1.637-3.75-4-3.75ZM5.75 12h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1 0-1.5ZM6 15.25a.75.75 0 0 1 .75-.75h2.5a.75.75 0 0 1 0 1.5h-2.5a.75.75 0 0 1-.75-.75Z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;Tip&lt;/p&gt;&lt;p&gt;If anything, I don’t think I will never open a Merge Request with an empty
description ever again.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;And that, kids, is how I released
&lt;a href=&quot;https://github.com/lthms/cohttp-connpool-eio&quot; class=&quot;hover-rose&quot; marked=&quot;&quot;&gt;&lt;code class=&quot;hljs&quot;&gt;cohttp-connpool-eio.0.1&lt;/code&gt;&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Wrapping-up a Minimal Ollama Chat&lt;/h3&gt;
&lt;p&gt;Integrating &lt;code class=&quot;hljs&quot;&gt;cohttp-connpool-eio&lt;/code&gt; in my &lt;code class=&quot;hljs&quot;&gt;ocaml-ollama&lt;/code&gt; project led me to find a
bug in the former. More specifically, the &lt;code class=&quot;hljs language-ocaml&quot;&gt;&lt;span class=&quot;hljs-type&quot;&gt;Cohttp_connpool_eio&lt;/span&gt;.warm&lt;/code&gt;
function that can be used to pre-populate a new pool was doing so by performing
a specified &lt;code class=&quot;hljs&quot;&gt;HEAD&lt;/code&gt; request to the host as many time as the pool size&lt;label for=&quot;fn7&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn7&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-right sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;In a later iteration of the library, &lt;code class=&quot;hljs&quot;&gt;warm&lt;/code&gt; only establishes
connections, and does not perform any unnecessary HTTP requests. &lt;/span&gt;
&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;It worked well against both &lt;code class=&quot;hljs&quot;&gt;https://www.google.com&lt;/code&gt; and
&lt;code class=&quot;hljs&quot;&gt;https://soap.coffee/~lthms&lt;/code&gt;, but when I tried with the Ollama server, it
decided to hang. Why?&lt;/p&gt;
&lt;p&gt;Well, I tried asking my new friends the LLMs, but didn’t get any answer I felt
confident with. At this point, my trust in their EIO expertise was rather low,
and I was more skimming through their answer to find a lead I would follow
myself than anything else. In the end, I completely dropped the LLMs here, and
went back to what I usually do: experimenting, and reading code.&lt;/p&gt;
&lt;p&gt;I reproduced the issue with &lt;code class=&quot;hljs&quot;&gt;curl&lt;/code&gt;: &lt;code class=&quot;hljs&quot;&gt;curl -X HEAD&lt;/code&gt; hangs as well with Ollama,
while &lt;code class=&quot;hljs&quot;&gt;curl --head&lt;/code&gt; does not. The former tries to read the response body, based
on the response headers (&lt;em&gt;e.g.&lt;/em&gt;, &lt;code class=&quot;hljs&quot;&gt;content-length&lt;/code&gt;). The latter doesn’t, because
it knows &lt;code class=&quot;hljs&quot;&gt;HEAD&lt;/code&gt; always omits the body. I am not sure &lt;em&gt;why&lt;/em&gt;  the hanging
behavior does not show for &lt;code class=&quot;hljs&quot;&gt;curl -X HEAD https://www.google.com&lt;/code&gt;, though.&lt;/p&gt;
&lt;p&gt;But anyway, once the bug was fixed, I could return to playing with Ollama.&lt;/p&gt;
&lt;p&gt;I then decided to implement a helper to call &lt;a href=&quot;https://github.com/ollama/ollama/blob/main/docs/api.md#generate-a-completion&quot; class=&quot;hover-periwinkle&quot; marked=&quot;&quot;&gt;&lt;code class=&quot;hljs&quot;&gt;POST /api/generate&lt;/code&gt;&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#github&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;.
It is the simplest way with Ollama to generate an LLM’s answer from a prompt.
Interestingly enough, it is a “streamed” RPC using the &lt;code class=&quot;hljs&quot;&gt;application/x-ndjson&lt;/code&gt;
content type. Instead of computing the answer &lt;em&gt;before&lt;/em&gt; sending it to the
client, the server instead sends JSON-encoded chunks (&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Transfer-Encoding#chunked&quot; class=&quot;hover-coral&quot; marked=&quot;&quot;&gt;&lt;code class=&quot;hljs&quot;&gt;transfer-encoding: chunked&lt;/code&gt;&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;I tried to implement that with &lt;code class=&quot;hljs&quot;&gt;cohttp-eio&lt;/code&gt;, and it failed miserably with
obscure parsing error messages.&lt;/p&gt;
&lt;p&gt;After a bit of debugging, it became clear that &lt;code class=&quot;hljs language-ocaml&quot;&gt;&lt;span class=&quot;hljs-type&quot;&gt;Eio&lt;/span&gt;.&lt;span class=&quot;hljs-type&quot;&gt;Buf_read&lt;/span&gt;.parse&lt;/code&gt; was
not behaving as I thought it was, which made me feel paranoid about how
&lt;code class=&quot;hljs&quot;&gt;cohttp-connpool-eio&lt;/code&gt; handles connection releases. In the end, I had to unpack
how the &lt;code class=&quot;hljs language-ocaml&quot;&gt;&lt;span class=&quot;hljs-type&quot;&gt;Cohttp_eio&lt;/span&gt;.&lt;span class=&quot;hljs-type&quot;&gt;Body&lt;/span&gt;.t&lt;/code&gt; work under the hood wrt.
&lt;code class=&quot;hljs language-ocaml&quot;&gt;&lt;span class=&quot;hljs-type&quot;&gt;End_of_file&lt;/span&gt;&lt;/code&gt; to move on. Once again, my LLM friends weren’t
particularly helpful: they were hallucinating &lt;code class=&quot;hljs&quot;&gt;Buf_read&lt;/code&gt; functions, and never
considered to mention that &lt;code class=&quot;hljs&quot;&gt;parse&lt;/code&gt; only works for complete response.&lt;/p&gt;
&lt;div class=&quot;markdown-alert markdown-alert-tip&quot;&gt;&lt;p class=&quot;markdown-alert-title&quot;&gt;&lt;svg class=&quot;octicon octicon-light-bulb mr-2&quot; viewbox=&quot;0 0 16 16&quot; version=&quot;1.1&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M8 1.5c-2.363 0-4 1.69-4 3.75 0 .984.424 1.625.984 2.304l.214.253c.223.264.47.556.673.848.284.411.537.896.621 1.49a.75.75 0 0 1-1.484.211c-.04-.282-.163-.547-.37-.847a8.456 8.456 0 0 0-.542-.68c-.084-.1-.173-.205-.268-.32C3.201 7.75 2.5 6.766 2.5 5.25 2.5 2.31 4.863 0 8 0s5.5 2.31 5.5 5.25c0 1.516-.701 2.5-1.328 3.259-.095.115-.184.22-.268.319-.207.245-.383.453-.541.681-.208.3-.33.565-.37.847a.751.751 0 0 1-1.485-.212c.084-.593.337-1.078.621-1.489.203-.292.45-.584.673-.848.075-.088.147-.173.213-.253.561-.679.985-1.32.985-2.304 0-2.06-1.637-3.75-4-3.75ZM5.75 12h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1 0-1.5ZM6 15.25a.75.75 0 0 1 .75-.75h2.5a.75.75 0 0 1 0 1.5h-2.5a.75.75 0 0 1-.75-.75Z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;Tip&lt;/p&gt;&lt;p&gt;My personal conclusion is that ChatGPT and Gemini quickly show their limits
for non-trivial programming task involving Eio and its ecosystem.
I am really curious to understand why. Do they keep hallucinating functions
because Eio is a really generic name, and maybe they are mixing context from
the Python library with the OCaml one? Or is it because the API of Eio has
changed a lot over the years?&lt;/p&gt;
&lt;p&gt;I am also wondering how, as a the author of a library, I can fix a similar
situation. Assuming ChatGPT starts assuming false statements about
&lt;code class=&quot;hljs&quot;&gt;cohttp-connpool-eio&lt;/code&gt; for instance, how do I address this? I suspect being
“LLMs-friendly” will be increasingly important for a software library’s
success.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;In the end, ChatGPT and Gemini were just another source of inputs, not the main
driver of my development process.&lt;/p&gt;
&lt;h2&gt;Putting Everything Together&lt;/h2&gt;
&lt;p&gt;Turns out, you really need just one RPC to generate a summary for a text input,
so it wasn’t long before I could chain everything. I pulled
&lt;code class=&quot;hljs&quot;&gt;mistral:7b-instruct-v0.2-q4_K_M&lt;/code&gt; (over a suggestion by ChatGPT, if I remember
correctly), and got a summary from the video I had downloaded.&lt;/p&gt;
&lt;p&gt;Just kidding. Out of nowhere, I decided to pursue yet another side quest, and
gave a try to the fancy &lt;a href=&quot;https://dune.readthedocs.io/en/stable/tutorials/dune-package-management/dependencies.html&quot; class=&quot;hover-mint&quot; marked=&quot;&quot;&gt;&lt;code class=&quot;hljs&quot;&gt;dune pkg lock&lt;/code&gt;&amp;nbsp;&lt;span class=&quot;icon&quot;&gt;&lt;svg&gt;&lt;use href=&quot;/~lthms/img/icons.svg#external-link&quot;&gt;&lt;/use&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/a&gt;
command. &lt;em&gt;Then&lt;/em&gt; I was able to &lt;a href=&quot;/~lthms/files/RhinocerosSummary.html&quot; class=&quot;hover-lavender&quot; marked=&quot;&quot;&gt;generate my
summary&lt;/a&gt;, using the following prompt.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs&quot;&gt;Generate a summary of the raw transcript of a video provided after
this paragraph. The transcript may be in a language that is not
English, but the summary should always be in English. You should
adopt a neutral point of view (i.e., even if the transcript speaks
in the first person, you should always use the third person). Each
line is an utterance. Keep the summary short and engaging, your
goal is to provide a good overview of what was said.

----

{Vosk output}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And with this, it was time to wrap-up. And what better way to do so than to
write this little journal entry? So I did, and when I was half way through my
first draft, I fired a new chat buffer to ask for advice from my new friend
Gemini Flash.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;#buffer Here is a very preliminary, incomplete draft of a blogpost. Can you try to
anticipate if it will find an audience?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It wasn’t long before Gemini turned me down.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;My apologies, but I cannot anticipate whether this blog post will find an
audience. My capabilities are focused on programming-related tasks like code
explanation, review, generation, and tool execution within the Neovim
environment.&lt;/p&gt;
&lt;p&gt;Let me know if you have any questions about the code itself, or need
assistance with Neovim.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;🥲&lt;label for=&quot;fn8&quot; class=&quot;sidenote-number margin-toggle&quot;&gt;&lt;/label&gt;&lt;input id=&quot;fn8&quot; type=&quot;checkbox&quot; class=&quot;margin-toggle&quot;&gt;&lt;span class=&quot;note-left sidenote note&quot;&gt;&lt;span class=&quot;footnote-p&quot;&gt;Fortunately, ChatGPT was less opinionated. &lt;/span&gt;
&lt;/span&gt;.&lt;/p&gt;
&lt;h2&gt;Final Words&lt;/h2&gt;
&lt;p&gt;Although I had already used ChatGPT and other models in the past, this was the
first time I tried to make them a central part of my workflow. I learned a lot
during this experiment, and I now have an integrated setup I enjoy using.&lt;/p&gt;
&lt;p&gt;I need to keep digging. Try more models (there are a lot of those now). And get
better at writing good prompts which do not lead the LLMs astray. They are here
to stay, after all. I better learn how to take the most from them.&lt;/p&gt;
        
      </description>
    </item>
    
    
  </channel>
</rss>
