esc
Anthology / Yagnipedia / The Second Consumer

The Second Consumer

The Forcing Function That Turns Generalization Into Evidence
Principle · First observed 2015 (node_exporter's second collector); rediscovered 2026 (Solid's Internal AIOps) · Severity: Architectural

The Second Consumer is the architectural principle that no interface is real until two distinct consumers use it. The first consumer is optimism. The second consumer is evidence. Generalization attempted without a second consumer in the room is theater, and the stage is the PR description.

The principle addresses a specific failure mode in software architecture: when a developer builds a framework to serve one consumer, the developer — with the best intentions, and often with impressive discipline — encodes that consumer’s particular assumptions as though they were universal. The code compiles. The tests pass. The interface, rendered as a UML diagram, looks honest. Then the second consumer arrives, and approximately ten things everyone assumed were load-bearing turn out to be decorative, while approximately three things everyone ignored turn out to be load-bearing in ways that nobody wrote down because nobody knew they were doing it.

“THE FIRST CONSUMER PROVES IT WORKS
THE SECOND CONSUMER PROVES IT GENERALIZES

THE FIRST WAS OPTIMISM
THE SECOND WAS EVIDENCE”

The Lizard, on a scroll that arrived at an interface review and caused someone to cancel the interface review

The Problem With One

A developer writes a framework for Customer One. Customer One has opinions. These opinions have names — TimeoutSeconds, RetryPolicy, AuthToken — and the names make the opinions look like decisions. They are not decisions. They are Customer One’s environment, mistaken for a design.

The developer cannot see this. The developer is, by construction, inside Customer One’s world. Every interface the developer chooses feels general because every constraint the developer is aware of is covered by it. The constraints the developer is not aware of are, definitionally, the ones Customer One does not have. This is not incompetence; it is geometry. You cannot see the shape of a room from inside its only corner.

The framework looks extensible. The documentation uses words like “pluggable” and “modular.” The author uses the phrase “clean abstraction” in the PR description, and no reviewer pushes back, because the code does in fact look clean. Clean code is an adjective that describes the reader’s emotional response to the code. It is not a claim about the code’s generality. These are routinely confused.

A year passes. Customer Two arrives. Customer Two is plausibly similar to Customer One, which is why someone said “we already have a framework for this.” Customer Two is also subtly different, in exactly the ways the framework did not anticipate, because the framework did not anticipate anything — it memorized Customer One and called the memorization an interface.

The code does not compile for Customer Two. Or worse: it compiles, it passes, it ships, and six weeks later someone discovers that the “framework” is silently doing the wrong thing for Customer Two because one of Customer One’s assumptions has leaked through a field whose name implied universality. The field is called Region. Customer Two does not have a Region. The field is set to an empty string, and in Customer One’s code path the empty string is a sentinel meaning “do the default thing,” and the default thing is subtly wrong for Customer Two, and nobody notices for a quarter.

This is the cost of the interface that was never real.

The node_exporter Case

The canonical example in the open source record is node_exporter, the Prometheus project’s system metrics collector, which was designed in 2015 around the idea of “collectors” — small modules that each exposed a category of metrics. There was a CPU collector. There was a memory collector. There was a disk collector. For a brief moment, in the very earliest commits, there was essentially one collector and a plan to have more.

The plan was called “the plugin interface,” and the plan was theoretical, and the plan would have remained theoretical for as long as there was exactly one plugin, which is to say forever, because theoretical interfaces that exist for one consumer do not generalize — they merely describe.

Then the second collector was written. This is the moment the interface became real, which is another way of saying the moment the interface became a compiler error. The assumptions the first collector had silently made about registration order, about metric namespacing, about error handling, about when to run and when not to run — each of these was suddenly contradicted by a second collector that had its own, equally valid, equally implicit assumptions. The interface that survived the resulting negotiation is, in essence, the interface the ecosystem still runs on ten years later, running in tens of thousands of environments and underlying most of what people mean when they say “we have observability.”

The interface was not designed. The interface was excavated — dug out of the space between two concrete implementations by the process of making both of them work through the same hole.

“I have reviewed many frameworks that were designed for one consumer. They are all beautifully designed. They are all wrong in the same way. The way is: they have shapes that match their one consumer. I cannot tell, from inside them, that this is what they are. The developer cannot tell either. Only the second consumer can tell, and only by bleeding.”
A Passing AI, footnoting a code review it had been asked to write and then expanded into a treatise nobody requested

The Forcing Function

The ordinary response to this observation is: “Yes, of course, we’ll generalize when the second consumer arrives.”

This response is wrong, and it is wrong in a specific way. By the time the second consumer arrives, the first consumer has been in production for six months, has accumulated customers, has accumulated commitments, has accumulated a change-advisory-board process, and has accumulated a developer who — entirely reasonably — will defend the current shape of the code because changing it breaks Customer One. The interface was not real six months ago; now it is both unreal and immovable.

The forcing function is to not plan a framework at all. The forcing function is to plan two consumers of a framework, and to schedule them close enough in time that the assumptions made for the first are still hot — still visible, still editable, still uncommitted to production — when the second tests them.

This is a scheduling discipline, not a technical one. The technical part is almost uninteresting. The hard part is the calendar. The hard part is refusing to declare victory when the first consumer lights up, refusing to move the team off the framework onto something new, refusing to let the first consumer’s production traffic become a reason the framework cannot be reshaped. The hard part is holding the framework in the soft-clay state for exactly as long as it takes the second consumer to arrive and press their fingers into it.

After that, the framework hardens. It hardens into whatever shape the two consumers jointly demanded. That shape is the interface. That shape was not known beforehand, could not have been known beforehand, and was produced by a process that the participants, at the time, will not have experienced as “designing an interface” — they will have experienced it as “getting two things to work at once and being slightly annoyed about it.”

The Solid Case

The principle surfaces with uncommon clarity in the Solid codebase, where it appears not as advice but as project plan.

The Solid Data Governance initiative — the top-level arc that turns Solid from a compliance tool into a platform that observes itself — is sequenced so that the Platform Ingestion Foundation ships first, and Solid Internal AIOps ships immediately after. The framework’s first consumer is the set of third-party feeds that power customer-facing compliance. The framework’s second consumer is Solid itself, watching its own agents and tools and jobs through the same ingestion pipe.

The order is not accidental. The order is the whole thing.

If Solid Internal AIOps were added a year after Platform Ingestion Foundation, the feed framework would, by then, have hardened around the shape of its first consumers — compliance sources — and the assumptions they imply. Field names would tacitly assume compliance semantics. Retry behavior would be tuned for compliance-source flakiness. Timestamp fields would assume the timezones compliance sources emit. Solid, trying to feed itself into its own framework, would discover that the framework is really a Comply-shaped binary in a feed-shaped coat, and either the framework would be rewritten (expensive) or Solid would grow a second ingestion pipe for its own telemetry (worse — now there are two ingestion pipes, which is the beginning of having an infinite number of ingestion pipes).

Sequencing Solid Internal AIOps immediately after Platform Ingestion Foundation is not a feature decision. It is architectural review in executable form. It says: if this framework can accommodate Solid observing itself, it is a framework; if it cannot, it was never a framework, and we would rather discover that now, when the clay is still soft, than in a year when the clay is a load-bearing wall.

“The feed framework that cannot ingest Solid itself is not a feed framework. It is a feed framework story we told ourselves. The test is not whether the story is beautiful. The test is whether the second consumer can live in it.”
riclib, quoted at a planning meeting that was supposed to last forty minutes and ended in nineteen

The Dog Food Confusion

“Eat your own dog food” is the principle most frequently confused with The Second Consumer. The phrases rhyme; their consequences do not.

Dog food is a validation discipline. A team uses its own product, notices the product is bad, and fixes it. This is good, and it is not The Second Consumer. Dog food can be retrofitted. A team can start using its own product three years after the product shipped, can accumulate the usual embarrassments, and can correct them over time. Nothing about dog food requires the team’s consumption to reshape the product; many dog-fed products have remained badly shaped for years while their builders complained about the shape between meetings and continued to ship it.

The Second Consumer must arrive in time to reshape the framework — not merely to use it, not merely to complain about it, but to bend it, while it is still bendable. Dog food is a signal. The Second Consumer is a forcing function. One produces embarrassment. The other produces generalization. Generalization is the rarer and more valuable output. Do not confuse them, especially in slide decks to executives, many of whom have heard of the first phrase and none of whom have heard of the second.

The Squirrel’s Objection

The Caffeinated Squirrel, alerted to the existence of The Second Consumer, proposed that the principle be operationalized via a FrameworkDesignValidationProtocol consisting of seven stages, a chartered committee, a gating checklist with twenty-three items, a quarterly architecture review that would produce a scorecard, and a rotating role called the SecondConsumerAdvocate whose job it would be to lobby for the interests of imagined future consumers at meetings. The Squirrel suggested that the protocol itself would need a framework to manage it, and drafted the framework while the rest of the team was at lunch.

The Lizard, when shown the draft, issued a scroll reading, in its entirety, “WRITE THE SECOND CONSUMER.”

The Squirrel asked which second consumer.

The Lizard said: “ANY SECOND CONSUMER. THE ACT OF WRITING IS THE PROTOCOL. THE WRITING IS THE COMMITTEE. THE BLEEDING IS THE CHECKLIST.”

The Squirrel found this anticlimactic. The Squirrel wanted the answer to be sophisticated. The Squirrel wanted the answer to involve a diagram with swim-lanes. The answer was “write the second thing,” and the Squirrel went and stared out the window for twelve minutes and then, eventually, opened a pull request titled feat: second collector (memory, minimal), and in the writing of that collector, a dozen silent assumptions in the first collector became visible, and each of them was fixed, and the framework became a framework. The Squirrel did not admit the principle had worked. The Squirrel insisted the fix was “obvious in retrospect.” The Lizard did not require an admission.

Kindred Principles

The pattern has surfaced in many disguises across the history of software, which is the usual fate of load-bearing insights.

Conway’s Law implies The Second Consumer indirectly: two teams negotiating an interface produce a real interface, because each team will catch the other’s implicit assumptions; one team designing one interface produces a wish list, because there is no one in the room whose assumptions differ from the author’s. The Second Consumer generalizes this observation from organizational structure to implementation structure: the forcing function is the same whether the second reader is a team or a piece of code. The code is, in the end, cheaper to iterate on than the team, which is why The Second Consumer is the more practical lever of the two.

Gall’s Law rhymes with The Second Consumer without stating it: “a complex system that works is invariably found to have evolved from a simple system that worked.” The hinge word is worked. A simple system that worked for exactly one consumer did not work in the generative sense — it merely functioned. Gall’s Law is quietly assuming that the simple system worked for more than its author, which is another way of saying it had a second consumer, which is another way of saying the evolution was possible because the generalization had already happened in miniature. A system that worked for one consumer does not evolve into a system that works for many. It evolves into the same system, slightly bigger, still only working for one. The Second Consumer is the unstated precondition of Gall’s Law’s hopeful arc.

YAGNI is the bounded version of the principle: if you have no consumers at all, do not build anything; if you have one consumer, build for that consumer. The Second Consumer extends YAGNI into the generalization case: the moment you are tempted to call something a framework, the discipline is not “design carefully” — the discipline is “produce the second consumer.” If the second consumer cannot be produced, the framework cannot be justified, and what is wanted is not a framework but a function.

Mythology Driven Development (MDD™) rhymes with The Second Consumer from the opposite direction: the mythology works because multiple readers — humans, LLMs, future selves — have to interpret the same narrative, and any one of them being unable to make the story compile surfaces the incoherence. A mythology with one reader is a diary. A mythology with two is a culture. The Yagnipedia entries are the framework; the reader you do not yet have is the second consumer; and the entries get better as the second consumer draws nearer, which is why they are written as though someone is about to show up.

Where It Does Not Apply

The Second Consumer is expensive enough that it should not be applied indiscriminately. Three cases where it does not apply:

Leaf code. A function with exactly one caller, in a codebase where no other caller is plausible, does not need The Second Consumer. It needs YAGNI. The invocation “but what if there’s a second caller later?” is the Squirrel’s trap; the answer is “add The Second Consumer then, by which time you will know what shape the caller wants.”

Legally binding interfaces. APIs specified by external contract — OpenAPI schemas handed to partners, regulatory filings, published network protocols — cannot be shaped by second consumers in the The-Second-Consumer sense, because the shape is fixed by the contract and the “second consumer” is a third party whose code you do not control. The Second Consumer applies to the implementation behind such an interface, not to the interface itself. This is fine. Many interfaces in a codebase are internal. The external ones are the minority.

One-off scripts. A bash script written in anger on a Friday, used once, does not need The Second Consumer. It needs to exist briefly and then be deleted. Some people delete such scripts. Some people commit them to scripts/, where they become the second consumer of every refactor forever. The discipline “delete one-off scripts” is related to but distinct from The Second Consumer and is not covered further in this entry.

The Asymmetry

The case for The Second Consumer reduces, ultimately, to an asymmetry of cost.

The cost of adding The Second Consumer — writing the second collector, sequencing Solid Internal AIOps right after Platform Ingestion Foundation, taking the team’s time to implement the feed framework against two producers instead of one — is bounded. It can be estimated. It can be budgeted. It will show up in the sprint plan as a specific number of tickets, and those tickets will be completed, and the organization will know what they cost.

The cost of discovering, six months after shipping the first consumer, that you had only one consumer and your interface was not an interface, is unbounded. It includes the cost of rewriting the interface. It includes the cost of migrating the first consumer to the rewritten interface. It includes the cost of the bugs that accumulate during the migration. It includes the organizational cost of admitting that the framework was not a framework, which is often paid in currency not listed on the org chart. In the worst case, it includes the cost of not rewriting — of building the second consumer’s bespoke pipe parallel to the first, and then the third’s, and then the fourth’s, until the organization is maintaining N pipes that were once going to be one framework, and the word “framework” has been quietly dropped from the team’s vocabulary.

This is why The Second Consumer is one of the highest-leverage architectural disciplines: the bounded cost is paid once, in advance, on purpose. The unbounded cost is paid many times, in pain, by accident. The discipline is not to avoid the cost — there is no path with no cost — but to pay the bounded one instead of the unbounded one, and to do so before the first consumer has made the decision irreversible.

“I BUILT A FRAMEWORK FOR ONE CONSUMER
AND THE CONSUMER WAS HAPPY

I BUILT A FRAMEWORK FOR TWO CONSUMERS
AND THE FRAMEWORK WAS REAL

THESE ARE NOT THE SAME OUTCOME
THEY ARE NOT EVEN THE SAME JOB”

The Lizard, on a scroll that arrived at the end of a planning session and was read aloud twice

Measured Characteristics

Interfaces called "plugin" that had exactly one plugin:       observed frequently
Frameworks designed for N consumers that had 1:               majority, historically
node_exporter collectors in the wild (approx, 2026):          60+
Consumers required before a framework "counts" as real:       2 (minimum, non-negotiable)
Months a framework can hide a single-consumer assumption:     many (observed: up to 18)
Frameworks that discovered they weren't frameworks:           many
Lines of framework code deleted when the second consumer
  arrived late (typical PR):                                  net-negative, often dramatically
Architects who know The Second Consumer by some name:         most, under different names
Architects who have scheduled it deliberately:                few
Improvement in interface quality when consumer #2 lands
  within 30 days of consumer #1:                              large (felt, not measured)
Improvement when consumer #2 lands 12 months later:           small (first consumer has calcified)
Solid subsystems sequenced with a Second Consumer:            Platform Ingestion Foundation → Internal AIOps
Squirrel-proposed protocols for validating frameworks:        1 (rejected)
Lizard scrolls on the topic:                                  several
Correct number of consumers to design a framework around:     2
Common number of consumers frameworks are designed around:    1
Distance between these two numbers:                           the entire problem

See Also