← Blog

The adapter is now cheaper than the library

A whole class of dependency, the thin wrapper that unifies several providers behind one interface, just flipped from asset to liability. A taxonomy of what the agent era kills and what it entrenches.

Machine-readable Download Markdown

I deleted a dependency this month that I would have defended vigorously two years ago. It was a well-maintained library whose job was to put one interface in front of several model providers. Sensible thing to want. I replaced it with about two hundred lines of my own adapter, generated in an afternoon, fitted exactly to the two providers I actually use.

I have been writing software for more than twenty-five years and I have spent most of that time arguing against exactly this move. Do not reinvent the wheel. Do not write code you can install. The library has handled edge cases you have not thought of. That advice was correct for a long time, and I think a specific slice of it just stopped being correct.

The convenience dependency

Not all dependencies are the same kind of thing, and we have been sloppy about this because for decades the answer was the same regardless: use the library.

The category I mean is narrow. Call it the convenience dependency: a package whose value is that someone else already wrote the obvious glue. Provider abstraction layers. Format adapters. Thin clients over a documented HTTP API. SDKs that mostly rename things. Configuration helpers.

What defines the category is that the code is not hard. It is tedious. There is no algorithm in it, no accumulated correctness, no security-critical parsing. The value proposition was never that the author was smarter than you. It was that the author had already spent the boring afternoon.

That was a real value proposition when the boring afternoon cost you an afternoon. It is a much weaker one when the boring afternoon costs forty minutes of agent time and produces something fitted precisely to your case rather than to the union of everyone's cases.

What you were actually buying, and what came with it

The trade was never just code for code. Taking a dependency means adopting the maintainer's release cadence, their idea of the right abstraction, their deprecation schedule, and their judgment about which upstream changes matter.

For a hard dependency that is a bargain. For a convenience dependency, the costs have been going up while the benefit has been going down, and the recent pressure is worth naming.

The abstraction is lowest common denominator by design. A layer that unifies five providers can only expose what all five share, which means the provider-specific capability you actually want is behind an escape hatch, if it is reachable at all. You wrote a wrapper around the wrapper. Many of us have.

The release cycle is now a real bottleneck. Model providers ship changes at a pace that library maintainers, most of them unpaid, cannot match. A capability lands, and you wait for a volunteer to expose it. When the underlying platform moves faster than the abstraction over it, the abstraction stops being a convenience and becomes a queue you are standing in.

And maintainers of popular packages are now absorbing a volume of low-quality generated pull requests that did not exist before. The maintenance burden of a small library went up for reasons entirely unrelated to the library. Some of these projects are going to get slower or stop, not because the problem got harder, but because the inbox did.

This got a public airing this week, in a long Hacker News discussion of RubyLLM, a library that puts one interface in front of the major model providers. One experienced commenter argued that the core connection layer should not be gated on a third party's release cycle, and said they had customized their own adapter so heavily they were glad it never became a dependency. It was not a consensus. The maintainer disputed that commenter's account of contributing upstream, and a separate thread argued the abstraction had earned its keep across provider switches. The comparison that stuck with me came from someone else entirely: choosing a database driver over an ORM. You give up portability you were never going to use, and you get full access to the one system you actually run.

The taxonomy

The useful output here is not "write more code yourself." It is a line, and I think the line is fairly clean.

Dependencies the agent era kills:

  • Thin provider abstractions and unification layers
  • Format adapters and serialization helpers
  • SDKs that are mostly a renaming of an HTTP API
  • Configuration and environment glue
  • Small utility packages that exist because the standard library is missing one function

Common property: no algorithmic depth, no specification, no trust boundary. The code is obvious and the only scarce input was patience.

Dependencies the agent era entrenches:

  • Anything cryptographic, without exception
  • Parsers for adversarial input
  • Database engines, query planners, storage layers
  • Anything implementing a real specification, where conformance is the product
  • Frameworks that are an architecture rather than a utility
  • Anything where a subtle bug is silent rather than loud

Common property: correctness is hard-won and hard to verify, or the failure mode is a breach rather than an error. Generating your own is not cheaper. It is cheaper right up until the day it is catastrophically more expensive, and you will not get a warning.

The test I use: if this code is wrong, do I find out from a stack trace or from a customer, and how long does that take? Loud failures are candidates for generation. Silent ones are not, at any price.

There is a middle tier I am less sure about: HTTP clients, logging, date handling. Real depth, real edge cases, but well-specified and loud when wrong. I keep using libraries for these and I would not argue hard with someone who did not.

Where this argument is weak

The strongest objection is that I am describing the creation of an enormous amount of unshared, unreviewed, undocumented code across the industry, and that this is how you get a maintenance crisis rather than an efficiency gain. The library was also a coordination mechanism. When it had a bug, one fix helped everyone. Now a thousand people each have their own version of that bug. I find this persuasive and I do not have a rebuttal beyond noting that it applies most to code that is shared and least to code that is bespoke by nature, which is the category I am carving out.

Second, "regenerate it" is easy to say and it hides ongoing cost. My adapter needs updating when a provider changes. That work does not disappear because an agent does it; it moves from the maintainer's calendar to mine, and I now own it forever. For two providers that is fine. At ten I would want the library back.

Third, I have a bias I should name. I enjoy writing this code. Deleting dependencies feels like craftsmanship. That is exactly the feeling I was warning about in the last piece I wrote on harness work, and I have to hold this argument a little more loosely because of it.

What this changes in my factory

New dependencies get classified before they get installed. Convenience or depth. If convenience, the default is to generate my own and I have to argue myself out of it rather than into it.

Existing convenience dependencies get reviewed when they block me, not on a schedule. The trigger is specific: if I am waiting on a release, or if I have written a wrapper around the wrapper, the dependency has already failed and I stop defending it.

Anything cryptographic, anything parsing untrusted input, anything with a specification stays a dependency permanently and is not subject to this reasoning at all. I want that rule to be boring and absolute so I never have to weigh it while tired.

Generated adapters get a test suite before they get used, which is the part that makes this honest. The library came with the maintainer's tests. If I am declining those, I owe myself the replacement.

What to ask before adding a dependency

  • Is the value here that this problem is hard, or that someone already spent the boring afternoon?
  • If this code is wrong, do I get a stack trace or a customer email?
  • Am I going to use more than the lowest common denominator of what it abstracts? If so, why am I abstracting?
  • Would waiting on this project's release cycle ever block me? Has it already?
  • If I generated this myself, would I write the tests? Honestly?
  • Does this cross a trust boundary or implement a specification? If yes, stop, take the dependency.

The old advice was to not reinvent the wheel. That was always about wheels, and we let it become about everything with a circular shape. Some of what we install is a wheel. A lot of it is a bracket that holds two things together, and brackets are cheap to make now.