The usual complaint about LangChain is boilerplate — too many imports, too many classes to subclass before you can do something simple, a chain definition that takes more lines than the prompt it's wrapping. That complaint is true, and it's also not the real cost. Boilerplate is annoying but cheap: you write it once, it sits there, and you mostly stop noticing it. The actual cost shows up later, and it shows up compounding.
Abstraction debt is real debt
Technical debt has a well-understood shape: you take a shortcut now, you pay interest on it later, usually in the form of slower changes and more bugs in that area of the code. Abstraction debt is the same mechanism applied to a framework's API surface instead of your own code, and it's more dangerous for one specific reason — you didn't choose it. You inherited it the day you picked the framework, and you keep paying interest on it for every workaround you write to route around an abstraction that doesn't match your actual problem.
Here's the pattern, almost every team that's spent six months in a chaining framework will recognize it: the framework gives you a callback hook for the thing you need (retry, logging, a custom tool, a guard condition). You implement it as a subclass or a registered handler. It works. Three months later you need a slightly different version of that same behavior somewhere else, and the cleanest way to get it is to subclass your own subclass, because the original abstraction wasn't built with your second use case in mind — it was built to be general enough to ship, not specific enough to be simple.
Where it actually bites you
Boilerplate costs you time once, when you write it. Abstraction debt costs you in three places that compound over the life of the project, and none of them show up on a time-to-first-demo benchmark:
- Debugging.A stack trace that has to pass through eight layers of callback indirection before it reaches the line you actually wrote is a stack trace that takes three times as long to read at 2am. The framework didn't cause your bug. It made finding your bug slower, every single time, for the life of the project.
- Onboarding.A new engineer doesn't just need to learn the framework. They need to learn your team's six months of workarounds for the parts of the framework that didn't fit — which are undocumented by definition, because nobody writes a README for "why we subclassed this twice."
- Migration cost that grows, not shrinks.The longer you stay, the more workarounds accumulate, and the harder it gets to tell which ones are fixing framework limitations versus fixing your own logic. Untangling that is strictly harder a year in than it is at month two — which means the "just rip it out" option gets more expensive every month you don't exercise it, not less.
Why this cost is so easy to underprice
Time-to-first-demo is visible and measurable, so it's what gets optimized for, by framework authors and by teams evaluating frameworks alike. Abstraction debt is invisible until it isn't — it accumulates quietly across dozens of small decisions, each one individually reasonable, and you don't feel the weight of it until you try to make a change that should be simple and discover it touches code in four places you'd forgotten existed.
That asymmetry — visible benefit up front, invisible cost spread over months — is exactly why teams keep making the same trade even when they've been burned by it before. It isn't a failure of judgment. It's a failure of the cost being measurable at decision time at all.
What that means for how we build n00dles
We treat "does this add a new way to extend the framework" as a cost, not a feature, every time we design something new. n00dles has one composition primitive for sequencing (>>), one for fan-out (parallel()), and one for routing (branch()). There's no callback registry, no chain-of-handlers pattern, no base class you need to subclass to customize behavior — if you need different behavior, you write a different function with the @agentdecorator and compose it differently. The thing you'd normally need a workaround for is usually just... a different function.
That's not an accident or a side effect of being small. It's the actual design target: keep the surface area small enough that six months from now, the codebase looks like what an engineer would write today reading the docs for the first time — not like an archaeological record of every edge case the team has hit since.
The question worth asking before you commit
Time-to-first-demo is a fine metric. It's just not the only one that matters, and it's the easiest one to over-index on because it's the only one you can measure before you've actually shipped anything. The question worth asking alongside it: what does debugging this in production, six months from now, with an engineer who didn't write the original code, actually look like?If the honest answer involves a callback chain eight layers deep, that's a cost — you're just paying it later instead of now.