<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Dependencies | Bas Nijholt</title><link>https://www.nijho.lt/tag/dependencies/</link><atom:link href="https://www.nijho.lt/tag/dependencies/index.xml" rel="self" type="application/rss+xml"/><description>Dependencies</description><generator>Hugo</generator><language>en-us</language><copyright>© 2026</copyright><lastBuildDate>Mon, 19 Oct 2026 00:00:00 +0000</lastBuildDate><image><url>https://www.nijho.lt/media/icon_hu_408a8f6cef43e327.png</url><title>Dependencies</title><link>https://www.nijho.lt/tag/dependencies/</link></image><item><title>Roll your own, fork the rest 🍴</title><link>https://www.nijho.lt/post/roll-your-own-fork-the-rest/</link><pubDate>Mon, 19 Oct 2026 00:00:00 +0000</pubDate><guid isPermaLink="false">/post/roll-your-own-fork-the-rest/</guid><description>&lt;h2 id="introduction-a-follow-up-to-something-i-wrote"&gt;Introduction: a follow-up to something I wrote &lt;a class="anchor" href="#introduction-a-follow-up-to-something-i-wrote" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;In April 2025 I published &lt;a href="https://www.nijho.lt/post/dependencies/"&gt;Confessions of an asymmetric hypocrite&lt;/a&gt;, about my split personality on dependencies.&#10;Strict minimalism for the libraries I publish, and then, for applications:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;For &lt;em&gt;applications&lt;/em&gt; (especially CLIs in isolated environments), I&amp;rsquo;m far more liberal with dependencies to build features quickly.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;I also wrote that I &amp;ldquo;strongly prefer dependencies that are already widely adopted and trusted by the community&amp;rdquo; because &amp;ldquo;these are less likely to cause unexpected issues&amp;rdquo;.&lt;/p&gt;&#10;&lt;p&gt;Then I spent a year building &lt;a href="https://www.nijho.lt/post/mindroom/"&gt;MindRoom&lt;/a&gt;, which is an application, exactly the category where I said I could relax.&lt;/p&gt;&#10;&lt;p&gt;I want to report back.&#10;&amp;ldquo;Prefer &lt;span class="hover-tip" data-tooltip="Code that so many people already run in production that the common bugs have been found and fixed before you get there." tabindex="0"&gt;battle-tested&lt;/span&gt; dependencies&amp;rdquo; was the right rule.&#10;I just had the wrong idea of what battle-tested means.&#10;I thought a project with tens of thousands of GitHub stars qualified.&#10;Now I think it means &lt;strong&gt;millions of downloads, every day.&lt;/strong&gt;&lt;/p&gt;&#10;&lt;details class="toc" open&gt;&lt;summary&gt;Table of contents&lt;/summary&gt;&lt;nav id="TableOfContents"&gt;&#10; &lt;ul&gt;&#10; &lt;li&gt;&lt;a href="#introduction-a-follow-up-to-something-i-wrote"&gt;Introduction: a follow-up to something I wrote&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#1-stars-measure-interest-not-usage"&gt;1. Stars measure interest, not usage&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#2-a-popular-protocol-is-not-a-popular-library"&gt;2. A popular protocol is not a popular library&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#3-i-checked-the-rule-against-my-own-code"&gt;3. I checked the rule against my own code&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#4-roll-your-own-the-library-gives-you-the-easy-10"&gt;4. Roll your own: the library gives you the easy 10%&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#5-fork-your-own-when-the-problem-is-hard"&gt;5. Fork your own: when the problem is hard&lt;/a&gt;&#10; &lt;ul&gt;&#10; &lt;li&gt;&lt;a href="#agno-patches-instead-of-a-fork"&gt;Agno: patches instead of a fork&lt;/a&gt;&lt;/li&gt;&#10; &lt;/ul&gt;&#10; &lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#6-i-am-not-dunking-on-open-source-including-mine"&gt;6. I am not dunking on open source, including mine&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#7-my-rule-for-the-next-uv-add"&gt;7. My rule for the next &lt;code&gt;uv add&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#10; &lt;/ul&gt;&#10;&lt;/nav&gt;&lt;/details&gt;&#10;&#10;&lt;aside class="callout callout-note"&gt;&lt;svg class="ico" viewBox="0 0 256 256" aria-hidden="true"&gt;&lt;path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm16-40a8,8,0,0,1-8,8,16,16,0,0,1-16-16V128a8,8,0,0,1,0-16,16,16,0,0,1,16,16v40A8,8,0,0,1,144,176ZM112,84a12,12,0,1,1,12,12A12,12,0,0,1,112,84Z"/&gt;&lt;/svg&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Stars measure interest, not usage. For every dependency, ask how many projects actually run the code you are about to rely on, and how much you rely on it.&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;Millions of downloads a day?&lt;/strong&gt; Depend on it.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Fewer, and it only gives you the easy part?&lt;/strong&gt; Roll your own. The hard part was going to be yours anyway.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Fewer, and hard to build?&lt;/strong&gt; Fork it, and treat the fork as yours.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;If I started MindRoom over, I would write my own HTTP requests to the model providers instead of using an AI framework.&lt;/p&gt;&#10;&lt;/div&gt;&lt;/aside&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&lt;strong&gt;Update 2026-10-31:&lt;/strong&gt; In the two weeks after publishing, I took my own advice twice.&#10;MindRoom&amp;rsquo;s knowledge search no longer uses ChromaDB, and code MindRoom owns has replaced Agno&amp;rsquo;s runtime.&#10;Agno is now only MindRoom&amp;rsquo;s tool library.&#10;The full stories are in &lt;a href="#comments"&gt;the comments&lt;/a&gt;.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;h2 id="1-stars-measure-interest-not-usage"&gt;1. Stars measure interest, not usage &lt;a class="anchor" href="#1-stars-measure-interest-not-usage" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;When I picked &lt;a href="https://github.com/agno-agi/agno" target="_blank" rel="noopener"&gt;Agno&lt;/a&gt; as MindRoom&amp;rsquo;s &lt;span class="hover-tip" data-tooltip="A library that wraps the model providers&amp;#39; APIs and adds agents, tools, memory, knowledge bases and session storage on top." tabindex="0"&gt;AI framework&lt;/span&gt;, it looked like the safe choice.&#10;Today it has over 42,000 stars and 6,000 forks.&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;&#10;&lt;p&gt;It also gets about &lt;strong&gt;78,000 downloads a day&lt;/strong&gt; on PyPI.&#10;&lt;a href="https://github.com/encode/httpx" target="_blank" rel="noopener"&gt;httpx&lt;/a&gt;, one of Python&amp;rsquo;s standard HTTP libraries, gets about &lt;strong&gt;25 million&lt;/strong&gt;.&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;&#10;&lt;p&gt;A star is something you click once, when a README looks exciting.&#10;A download happens every time some project installs the package.&lt;/p&gt;&#10;&lt;p&gt;Most of those downloads are &lt;span class="hover-tip" data-tooltip="Continuous integration: test and build pipelines that install a fresh environment on every push, often many times a day per project." tabindex="0"&gt;CI&lt;/span&gt;, not people, so downloads are not users either.&#10;But a package with millions of downloads a day is installed by a huge number of different projects.&#10;Many of them use the same functions you are about to use, so they hit the bugs before you do.&lt;/p&gt;&#10;&lt;p&gt;Battle-tested is also not the same as old.&#10;I use &lt;a href="https://github.com/astral-sh/uv" target="_blank" rel="noopener"&gt;uv&lt;/a&gt; for everything, and it has only been around for a &lt;span class="hover-tip" data-tooltip="Astral released the first version of uv in February 2024." tabindex="0"&gt;couple of years&lt;/span&gt;.&#10;I run into bugs in uv too, but there is simply no better alternative, and it gets about 3.6 million downloads a day.&lt;/p&gt;&#10;&lt;h2 id="2-a-popular-protocol-is-not-a-popular-library"&gt;2. A popular protocol is not a popular library &lt;a class="anchor" href="#2-a-popular-protocol-is-not-a-popular-library" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;The other place my old idea failed was &lt;a href="https://matrix.org" target="_blank" rel="noopener"&gt;Matrix&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;Building on Matrix felt obviously correct.&#10;An open protocol, about a decade old, with serious security reviews from &lt;span class="hover-tip" data-tooltip="Governments run their own Matrix deployments, for example Tchap for the French civil service and BwMessenger for the German armed forces." tabindex="0"&gt;large organizations and governments&lt;/span&gt;.&#10;Millions of people use it.&lt;/p&gt;&#10;&lt;p&gt;Matrix is still a good protocol and I am still on it.&#10;But even though millions of people use Matrix, there are only a few client libraries.&lt;/p&gt;&#10;&lt;p&gt;MindRoom streams agent replies by editing the same message many times a second.&#10;Almost every part of the ecosystem I touched had a problem with that: &lt;span class="hover-tip" data-tooltip="The most widely used Matrix app, made by the company behind the protocol." tabindex="0"&gt;Element&lt;/span&gt;, &lt;span class="hover-tip" data-tooltip="A Matrix web client focused on a simple, Discord-like interface. MindRoom&amp;#39;s chat app is a fork of it." tabindex="0"&gt;Cinny&lt;/span&gt;, &lt;span class="hover-tip" data-tooltip="The Python Matrix client library MindRoom&amp;#39;s agents use to log in, read and send messages." tabindex="0"&gt;matrix-nio&lt;/span&gt;, and the &lt;span class="hover-tip" data-tooltip="matrix-js-sdk, the library Element Web and Cinny are built on." tabindex="0"&gt;Matrix JavaScript SDK&lt;/span&gt;.&#10;In the JavaScript SDK, edits that arrived while a thread was still loading were silently dropped, so you saw stale text until you reloaded.&lt;sup id="fnref:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;&#10;&lt;p&gt;What counts is how many projects use the library you build on.&#10;Element&amp;rsquo;s users do not test matrix-js-sdk for you. Other people building on matrix-js-sdk do.&#10;It gets roughly 100,000 downloads a day. matrix-nio, the Python client, about 23,000.&lt;/p&gt;&#10;&lt;h2 id="3-i-checked-the-rule-against-my-own-code"&gt;3. I checked the rule against my own code &lt;a class="anchor" href="#3-i-checked-the-rule-against-my-own-code" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;I pulled the daily download numbers for the packages I use and looked for workarounds, patches and forks in my own source.&lt;sup id="fnref:4"&gt;&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref"&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;&#10;&lt;div class="table-wrap"&gt;&#10;&lt;table&gt;&#10;&lt;thead&gt;&#10;&lt;tr&gt;&#10;&lt;th&gt;Package&lt;/th&gt;&#10;&lt;th&gt;Downloads per day&lt;/th&gt;&#10;&lt;th&gt;What it cost me&lt;/th&gt;&#10;&lt;/tr&gt;&#10;&lt;/thead&gt;&#10;&lt;tbody&gt;&#10;&lt;tr&gt;&#10;&lt;td&gt;requests&lt;/td&gt;&#10;&lt;td&gt;49M&lt;/td&gt;&#10;&lt;td&gt;nothing I had to work around&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr&gt;&#10;&lt;td&gt;pydantic&lt;/td&gt;&#10;&lt;td&gt;35M&lt;/td&gt;&#10;&lt;td&gt;nothing&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr&gt;&#10;&lt;td&gt;numpy&lt;/td&gt;&#10;&lt;td&gt;33M&lt;/td&gt;&#10;&lt;td&gt;nothing&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr&gt;&#10;&lt;td&gt;httpx&lt;/td&gt;&#10;&lt;td&gt;25M&lt;/td&gt;&#10;&lt;td&gt;nothing&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr&gt;&#10;&lt;td&gt;aiohttp&lt;/td&gt;&#10;&lt;td&gt;18M&lt;/td&gt;&#10;&lt;td&gt;nothing&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr&gt;&#10;&lt;td&gt;FastAPI&lt;/td&gt;&#10;&lt;td&gt;17M&lt;/td&gt;&#10;&lt;td&gt;nothing&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr&gt;&#10;&lt;td&gt;chromadb&lt;/td&gt;&#10;&lt;td&gt;270K&lt;/td&gt;&#10;&lt;td&gt;workarounds for a bug that holds the GIL&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr&gt;&#10;&lt;td&gt;matrix-js-sdk&lt;/td&gt;&#10;&lt;td&gt;~100K&lt;/td&gt;&#10;&lt;td&gt;patched&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr&gt;&#10;&lt;td&gt;mem0ai&lt;/td&gt;&#10;&lt;td&gt;79K&lt;/td&gt;&#10;&lt;td&gt;largely reimplemented&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr&gt;&#10;&lt;td&gt;agno&lt;/td&gt;&#10;&lt;td&gt;78K&lt;/td&gt;&#10;&lt;td&gt;&lt;strong&gt;126 documented workarounds&lt;/strong&gt;&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr&gt;&#10;&lt;td&gt;matrix-nio&lt;/td&gt;&#10;&lt;td&gt;23K&lt;/td&gt;&#10;&lt;td&gt;forked&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Above a million a day, I had nothing to work around.&#10;Below it, every package on this list cost me a patch, a fork, a rewrite, or a pile of workarounds.&lt;/p&gt;&#10;&lt;p&gt;Take &lt;a href="https://github.com/chroma-core/chroma" target="_blank" rel="noopener"&gt;ChromaDB&lt;/a&gt;, the vector database behind MindRoom&amp;rsquo;s knowledge bases.&#10;In some cases it holds Python&amp;rsquo;s &lt;span class="hover-tip" data-tooltip="The Global Interpreter Lock: only one thread can run Python code at a time. Native code is supposed to release it while it waits, so other threads can keep going." tabindex="0"&gt;GIL&lt;/span&gt; during I/O, which blocks every other thread in the process.&#10;I opened &lt;a href="https://github.com/chroma-core/chroma/pull/7692" target="_blank" rel="noopener"&gt;a pull request&lt;/a&gt; to fix it, and nobody has looked at it yet.&#10;Instead of forking, I run every knowledge read in a short-lived child process, because a process can be killed and a blocked thread cannot.&lt;sup id="fnref:5"&gt;&lt;a href="#fn:5" class="footnote-ref" role="doc-noteref"&gt;5&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;&#10;&lt;p&gt;My fork of matrix-nio has about 8,900 lines added and 2,000 removed in the library code since the last upstream release.&lt;sup id="fnref:6"&gt;&lt;a href="#fn:6" class="footnote-ref" role="doc-noteref"&gt;6&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;&#10;&lt;p&gt;This does not mean every small package needs replacing.&#10;MindRoom pulls in a few hundred packages in total, and plenty of them have modest download numbers.&#10;The scrutiny should match how much I depend on a package.&#10;Something I use for one small, specific purpose is fine.&#10;Agno and matrix-nio are the base that MindRoom is built on, which is probably also why they hurt the most.&lt;/p&gt;&#10;&lt;h2 id="4-roll-your-own-the-library-gives-you-the-easy-10"&gt;4. Roll your own: the library gives you the easy 10% &lt;a class="anchor" href="#4-roll-your-own-the-library-gives-you-the-easy-10" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;Take memory libraries, &lt;a href="https://github.com/mem0ai/mem0" target="_blank" rel="noopener"&gt;Mem0&lt;/a&gt; and friends.&#10;I have &lt;a href="https://www.nijho.lt/post/file-based-rag-memory/"&gt;already written about this&lt;/a&gt;: I cloned LlamaIndex, LangChain, Letta, Mem0 and PydanticAI, studied how they work, and re-implemented the parts I needed. My memory system ended up &amp;ldquo;mostly modeled after Mem0&amp;rdquo;.&lt;/p&gt;&#10;&lt;p&gt;The core idea is not hard.&#10;With &lt;span class="hover-tip" data-tooltip="Retrieval-augmented generation: look up relevant text first and paste it into the prompt, so the model answers from your documents instead of from memory." tabindex="0"&gt;RAG&lt;/span&gt;, you chunk, embed, retrieve, re-rank and reconcile. Once you have read a couple of implementations you can write it, and afterwards you understand every line.&lt;/p&gt;&#10;&lt;p&gt;The same is true of an agent framework.&#10;It builds a JSON payload, posts it to an HTTP endpoint, parses a &lt;span class="hover-tip" data-tooltip="Providers send the reply as server-sent events: a long-lived HTTP response with one small JSON chunk per token or tool-call fragment." tabindex="0"&gt;streaming response&lt;/span&gt;, keeps a list of messages, and loops when the model asks for a tool call.&#10;With AI, that is a day or two of work.&lt;/p&gt;&#10;&lt;p&gt;The hard part is making it reliable at scale: never blocking the &lt;span class="hover-tip" data-tooltip="In async Python, one thread runs all tasks by switching between them. A single call that blocks freezes every conversation at once." tabindex="0"&gt;event loop&lt;/span&gt;, and handling the crazy edge cases.&#10;With an agent system you end up building a very complicated state machine, and if you do not watch it, any combination of states is possible. Almost all of them are edge cases.&lt;/p&gt;&#10;&lt;p&gt;A library gives you the easy 10% and leaves you the hard 90%.&#10;And you do that 90% inside somebody else&amp;rsquo;s code, where you cannot fix the root cause.&lt;/p&gt;&#10;&lt;p&gt;MindRoom is now about 360,000 lines of Python, plus about 750,000 lines of tests.&lt;sup id="fnref:7"&gt;&lt;a href="#fn:7" class="footnote-ref" role="doc-noteref"&gt;7&lt;/a&gt;&lt;/sup&gt;&#10;A good part of that is code I wrote instead of depending on a library:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;Memory and knowledge:&lt;/strong&gt; about 15,600 lines across 50 files.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Conversation history and &lt;span class="hover-tip" data-tooltip="Summarizing older turns of a conversation so a long thread still fits in the model&amp;#39;s context window." tabindex="0"&gt;compaction&lt;/span&gt;:&lt;/strong&gt; about 8,400 lines.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;An OpenAI-compatible API:&lt;/strong&gt; about 2,300 lines, so MindRoom agents show up as selectable &amp;ldquo;models&amp;rdquo; in any chat frontend that speaks the OpenAI protocol.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Claude provider support:&lt;/strong&gt; about 3,000 lines.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;If I have to write the hard 90% either way, I might as well write the easy 10% too.&lt;/p&gt;&#10;&lt;h2 id="5-fork-your-own-when-the-problem-is-hard"&gt;5. Fork your own: when the problem is hard &lt;a class="anchor" href="#5-fork-your-own-when-the-problem-is-hard" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;I am not going to write my own &lt;span class="hover-tip" data-tooltip="Matrix encryption means per-device keys, the Olm and Megolm ratchets, cross-signing, device verification and key backup, all of which have to interoperate with every other client." tabindex="0"&gt;end-to-end encrypted&lt;/span&gt; Matrix SDK, or my own &lt;span class="hover-tip" data-tooltip="The server that stores your rooms and syncs them with other Matrix servers, like a mail server for chat." tabindex="0"&gt;homeserver&lt;/span&gt;.&#10;Those are hard, and also far below the line.&#10;So there I fork.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;I forked the Python Matrix client.&lt;/strong&gt; MindRoom ships its own fork of matrix-nio rather than the upstream package.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;I forked the Matrix server, twice.&lt;/strong&gt; First &lt;span class="hover-tip" data-tooltip="The original Matrix homeserver, written in Python by the Matrix.org team." tabindex="0"&gt;Synapse&lt;/span&gt;. When I ran into scaling problems I moved to &lt;a href="https://github.com/matrix-construct/tuwunel" target="_blank" rel="noopener"&gt;Tuwunel&lt;/a&gt; and forked that.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;I forked the web client.&lt;/strong&gt; &lt;a href="https://github.com/cinnyapp/cinny" target="_blank" rel="noopener"&gt;Cinny&lt;/a&gt; is the Matrix web client MindRoom uses.&lt;/p&gt;&#10;&lt;p&gt;None of these are AI projects. AI is just where I felt it most.&lt;/p&gt;&#10;&lt;p&gt;Whether a fork stays a fork depends on the maintainers.&#10;Tuwunel is extremely receptive to my patches, so I upstream anything that is general enough, and over the last six months I am its fourth most active contributor by commits.&lt;sup id="fnref:8"&gt;&lt;a href="#fn:8" class="footnote-ref" role="doc-noteref"&gt;8&lt;/a&gt;&lt;/sup&gt;&#10;Cinny has not accepted any of my pull requests, so I forked it and stopped trying to upstream.&#10;Same with &lt;span class="hover-tip" data-tooltip="An open-source, self-hosted ChatGPT-style web interface that can talk to many model providers." tabindex="0"&gt;LibreChat&lt;/span&gt;: I had patches open for over a year without a reply, so I forked.&lt;/p&gt;&#10;&lt;p&gt;Keeping a fork up to date used to be miserable.&#10;With an LLM doing the rebasing, pulling in the latest upstream is now cheap, though still a bit of a risk every time.&#10;The bigger cost is that once you fork, you are responsible for the whole codebase, including the parts you never changed.&lt;/p&gt;&#10;&lt;h3 id="agno-patches-instead-of-a-fork"&gt;Agno: patches instead of a fork &lt;a class="anchor" href="#agno-patches-instead-of-a-fork" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h3&gt;&#10;&lt;p&gt;I did not fork Agno. I &lt;span class="hover-tip" data-tooltip="Replacing a library&amp;#39;s functions or methods at runtime from your own code, without changing the installed package." tabindex="0"&gt;monkey-patched&lt;/span&gt; it.&#10;Agno is very actively developed, I have talked to the maintainer, and they know about these problems, so I hoped to upstream my fixes.&#10;I have filed 33 issues and pull requests there.&lt;sup id="fnref:9"&gt;&lt;a href="#fn:9" class="footnote-ref" role="doc-noteref"&gt;9&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;&#10;&lt;p&gt;That got out of hand.&#10;MindRoom now carries &lt;strong&gt;126 documented workarounds&lt;/strong&gt; for Agno across 59 files.&#10;Most of them live in 36 dedicated compatibility modules, about 7,200 lines of code, with the rest patched inline in 23 other files.&#10;Every one uses the same comment format, so I can &lt;a href="https://github.com/search?q=repo%3Amindroom-ai%2Fmindroom&amp;#43;AGNO_COMPAT&amp;amp;type=code" target="_blank" rel="noopener"&gt;find all of them with one search&lt;/a&gt;.&#10;Each one says what is wrong upstream, links the issue or PR, and says when I can delete it.&lt;sup id="fnref:10"&gt;&lt;a href="#fn:10" class="footnote-ref" role="doc-noteref"&gt;10&lt;/a&gt;&lt;/sup&gt;&#10;A few examples:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;Search failures become plausible empty results.&lt;/strong&gt; A failed knowledge search and an empty index return the same empty list, so my agents confidently told me things did not exist.&lt;sup id="fnref:11"&gt;&lt;a href="#fn:11" class="footnote-ref" role="doc-noteref"&gt;11&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Streamed tool calls get malformed or merged.&lt;/strong&gt;&lt;sup id="fnref:12"&gt;&lt;a href="#fn:12" class="footnote-ref" role="doc-noteref"&gt;12&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Reasoning is silently lost when tool calls are replayed.&lt;/strong&gt;&lt;sup id="fnref:13"&gt;&lt;a href="#fn:13" class="footnote-ref" role="doc-noteref"&gt;13&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Provider errors collapse into a generic 502&lt;/strong&gt;, which throws away the error code you need to decide whether to retry.&lt;sup id="fnref:14"&gt;&lt;a href="#fn:14" class="footnote-ref" role="doc-noteref"&gt;14&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;At this size, the patches amount to a fork.&lt;/p&gt;&#10;&lt;h2 id="6-i-am-not-dunking-on-open-source-including-mine"&gt;6. I am not dunking on open source, including mine &lt;a class="anchor" href="#6-i-am-not-dunking-on-open-source-including-mine" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;Everything I named is open source I use every day, for free, built by people doing their best.&#10;I filed issues and PRs, and where maintainers merged fixes I deleted my workarounds.&lt;/p&gt;&#10;&lt;p&gt;My own software is in the same category.&#10;Most new AI tooling is not very good, and MindRoom may well be included.&#10;It is large, fast-moving, and nowhere near millions of daily downloads.&#10;By my own rule, you should think twice before depending on it.&lt;/p&gt;&#10;&lt;p&gt;I also have to admit that I have not yet built a project the roll-your-own way from the start.&#10;So comparing &amp;ldquo;what I would do next time&amp;rdquo; with what I actually did is not entirely fair.&#10;This is what I think right now, and a year from now I may have to write another post correcting this one.&lt;/p&gt;&#10;&lt;h2 id="7-my-rule-for-the-next-uv-add"&gt;7. My rule for the next &lt;code&gt;uv add&lt;/code&gt; &lt;a class="anchor" href="#7-my-rule-for-the-next-uv-add" aria-label="Link to this section"&gt;#&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;I weigh three questions.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;How many projects use it?&lt;/strong&gt;&#10;Daily downloads of the actual package, not stars, and not how many people use the product built on top.&#10;In the millions: depend on it.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;How much do I rely on it?&lt;/strong&gt;&#10;Agno and matrix-nio are the base of MindRoom.&#10;A library I use in a CLI to format a question is not, and if I use it in a narrow way I do not need my own version.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;How hard is it, and how receptive is upstream?&lt;/strong&gt;&#10;If the library is something AI can write in a couple of days, roll my own.&#10;If it is hard, fork it.&#10;If the maintainers accept patches, upstream what is general enough.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;If I started MindRoom over today, I would write the HTTP requests to the model providers myself.&lt;/strong&gt;&#10;Not even the OpenAI or Anthropic SDKs.&#10;And even httpx is less safe than its download numbers suggest: in early 2026 its maintainer closed the issue tracker and discussions, and the latest stable release is from December 2024.&lt;sup id="fnref:15"&gt;&lt;a href="#fn:15" class="footnote-ref" role="doc-noteref"&gt;15&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;&#10;&lt;p&gt;In 2025 I wrote that the right answer is &amp;ldquo;it depends on the situation&amp;rdquo;, and I still believe that.&#10;I just had the wrong threshold.&#10;Battle-tested means millions of downloads a day.&#10;Below that: roll your own, and fork the rest.&lt;/p&gt;&#10;&lt;div class="footnotes" role="doc-endnotes"&gt;&#10;&lt;hr&gt;&#10;&lt;ol&gt;&#10;&lt;li id="fn:1"&gt;&#10;&lt;p&gt;From the GitHub API (&lt;code&gt;api.github.com/repos/agno-agi/agno&lt;/code&gt;) on 2026-10-02: 42,385 stars and 6,041 forks.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li id="fn:2"&gt;&#10;&lt;p&gt;PyPI numbers are the &lt;code&gt;last_day&lt;/code&gt; field of the &lt;a href="https://pypistats.org" target="_blank" rel="noopener"&gt;pypistats.org&lt;/a&gt; API (&lt;code&gt;/api/packages/&amp;lt;name&amp;gt;/recent&lt;/code&gt;), fetched on 2026-10-02, except uv, fetched on 2026-10-18 (3,648,827). The matrix-js-sdk number is npm&amp;rsquo;s weekly download count (about 730,000) divided by seven. Daily numbers wobble, so treat them as orders of magnitude.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li id="fn:3"&gt;&#10;&lt;p&gt;When events arrive for a thread that has not finished initializing, the SDK&amp;rsquo;s replay path never applies them, so an &lt;code&gt;m.replace&lt;/code&gt; edit (the event type Matrix uses for &amp;ldquo;this replaces message X&amp;rdquo;) is stored and never shown. I replayed a captured event trace through the SDK: unpatched, zero of seventeen edits reached the listeners; patched, all seventeen. MindRoom carries the fix as a &lt;code&gt;patch-package&lt;/code&gt; patch on the SDK&amp;rsquo;s &lt;code&gt;thread.ts&lt;/code&gt;.&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li id="fn:4"&gt;&#10;&lt;p&gt;For each direct dependency I looked at three things in the MindRoom repository: whether &lt;code&gt;pyproject.toml&lt;/code&gt; pins a fork instead of the upstream package, whether &lt;code&gt;patch-package&lt;/code&gt; patches it, and whether the source has workaround markers or comments pointing at upstream issues for it. The packages above a million downloads a day had none of those.&amp;#160;&lt;a href="#fnref:4" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li id="fn:5"&gt;&#10;&lt;p&gt;The PR is titled &amp;ldquo;Release GIL during metadata I/O&amp;rdquo;. On the MindRoom side, &lt;code&gt;knowledge/read_process.py&lt;/code&gt; starts the child process, and a TODO says to remove it once a Chroma release with the fix is pinned. A blocked thread holding the GIL cannot be interrupted from Python, but the kernel will always terminate a child process.&amp;#160;&lt;a href="#fnref:5" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li id="fn:6"&gt;&#10;&lt;p&gt;Compared with upstream matrix-nio 0.25.2 (released 2024-10-04): &lt;code&gt;git diff --shortstat&lt;/code&gt; on the library code gives 8,872 lines added and 1,966 removed across 52 files, from 89 commits, 76 of them mine. The fork is published on PyPI as &lt;code&gt;mindroom-nio&lt;/code&gt;.&amp;#160;&lt;a href="#fnref:6" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li id="fn:7"&gt;&#10;&lt;p&gt;Counted with &lt;code&gt;git ls-files&lt;/code&gt; and &lt;code&gt;wc -l&lt;/code&gt; on the MindRoom repository on 2026-10-18: 360,513 lines in 1,222 Python files under &lt;code&gt;src/&lt;/code&gt;, 750,657 lines under &lt;code&gt;tests/&lt;/code&gt;, and another 81,543 lines of TypeScript in the frontend. The per-feature numbers below come from the same count over the relevant directories.&amp;#160;&lt;a href="#fnref:7" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li id="fn:8"&gt;&#10;&lt;p&gt;Non-merge commits on Tuwunel&amp;rsquo;s main branch since 2026-04-18: Jason Volk 1,332, June Strawberry 325, dasha_uwu 55, me 42.&amp;#160;&lt;a href="#fnref:8" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li id="fn:9"&gt;&#10;&lt;p&gt;Issues and pull requests in &lt;a href="https://github.com/agno-agi/agno/issues?q=author%3Abasnijholt" target="_blank" rel="noopener"&gt;agno-agi/agno&lt;/a&gt; with me as the author, counted on 2026-10-02.&amp;#160;&lt;a href="#fnref:9" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li id="fn:10"&gt;&#10;&lt;p&gt;Every workaround starts with a comment like &lt;code&gt;# AGNO_COMPAT: Knowledge search hides provider failures as empty results.&lt;/code&gt;, followed by &lt;code&gt;Reason:&lt;/code&gt;, &lt;code&gt;Upstream issue:&lt;/code&gt;, &lt;code&gt;Upstream PR:&lt;/code&gt;, &lt;code&gt;Remove when:&lt;/code&gt; and the tests that cover it. The counts come from &lt;code&gt;grep&lt;/code&gt; over &lt;code&gt;src/&lt;/code&gt;: 126 &lt;code&gt;AGNO_COMPAT:&lt;/code&gt; markers in 59 files, and 7,160 lines in the 36 &lt;code&gt;agno_compat_*&lt;/code&gt; modules.&amp;#160;&lt;a href="#fnref:10" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li id="fn:11"&gt;&#10;&lt;p&gt;&lt;code&gt;Knowledge.search&lt;/code&gt; catches vector store and provider errors and returns &lt;code&gt;[]&lt;/code&gt;. &lt;a href="https://github.com/agno-agi/agno/issues/10150" target="_blank" rel="noopener"&gt;agno#10150&lt;/a&gt;, partially fixed by &lt;a href="https://github.com/agno-agi/agno/pull/10152" target="_blank" rel="noopener"&gt;agno#10152&lt;/a&gt;.&amp;#160;&lt;a href="#fnref:11" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li id="fn:12"&gt;&#10;&lt;p&gt;When a streamed tool-call index does not start at zero, Agno leaves empty placeholder calls. &lt;a href="https://github.com/agno-agi/agno/issues/8879" target="_blank" rel="noopener"&gt;agno#8879&lt;/a&gt;, fix proposed in &lt;a href="https://github.com/agno-agi/agno/pull/8880" target="_blank" rel="noopener"&gt;agno#8880&lt;/a&gt;.&amp;#160;&lt;a href="#fnref:12" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li id="fn:13"&gt;&#10;&lt;p&gt;With the OpenAI Responses API, Agno keeps only the last reasoning item, so it cannot be replayed with the tool calls it belonged to. &lt;a href="https://github.com/agno-agi/agno/issues/9960" target="_blank" rel="noopener"&gt;agno#9960&lt;/a&gt;; partial fixes landed in &lt;a href="https://github.com/agno-agi/agno/pull/10075" target="_blank" rel="noopener"&gt;agno#10075&lt;/a&gt; and &lt;a href="https://github.com/agno-agi/agno/pull/10395" target="_blank" rel="noopener"&gt;agno#10395&lt;/a&gt;.&amp;#160;&lt;a href="#fnref:13" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li id="fn:14"&gt;&#10;&lt;p&gt;Unclassified &lt;code&gt;ModelProviderError&lt;/code&gt; failures default to HTTP 502, so a real network blip and a permanent error look the same. &lt;a href="https://github.com/agno-agi/agno/issues/8869" target="_blank" rel="noopener"&gt;agno#8869&lt;/a&gt;, partially addressed by &lt;a href="https://github.com/agno-agi/agno/pull/8870" target="_blank" rel="noopener"&gt;agno#8870&lt;/a&gt;.&amp;#160;&lt;a href="#fnref:14" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li id="fn:15"&gt;&#10;&lt;p&gt;The latest stable release on &lt;a href="https://pypi.org/project/httpx/#history" target="_blank" rel="noopener"&gt;PyPI&lt;/a&gt; is 0.28.1, uploaded 2024-12-06. There have been 1.0 development pre-releases since, the latest in August 2026. The closing of issues and discussions was widely discussed in the Python community in March 2026.&amp;#160;&lt;a href="#fnref:15" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;/div&gt;&#10;</description></item></channel></rss>