I. The discomfort is the signal
Most arguments about programming paradigms are first-level thinking. "OOP is good because encapsulation." "FP is good because purity." These statements are not wrong, exactly. They are the kind of statements that are true enough to repeat and shallow enough to be useless, like telling an investor that buying quality companies is a good idea. Sure. At what price? Under what conditions? Holding what else?
Second-level thinking asks a different question: not "which paradigm is good" but "what does each paradigm actually buy me, what does it cost, and what is everyone else doing that I can quietly do better than?" The interesting opportunities, in markets and in architecture, live in the gap between what's true and what's commonly believed.
Here is what's commonly believed: object-oriented programming is the serious, professional default, the thing real engineers use for real systems, and functional programming is either an academic indulgence or a niche tool for data pipelines. Senior engineers will pull you aside and say, in a tone usually reserved for warning people away from day trading, "be careful, you still need OOP."
Here is what's true: the seniors are right, but not about the thing they think they're right about. And the discomfort you feel with class-heavy OOP is not a gap in your education. It is, in most cases, a correctly functioning instinct that has not yet been given its vocabulary.

One caveat before we start: paradigm arguments are like asset-class arguments. Nobody serious says "stocks are better than bonds," full stop. They say "given this client, this horizon, these liabilities, here's the mix." Everything below should be read in that spirit. We're constructing a portfolio, not joining a church.

II. The term "OOP" was hijacked, and nobody held a funeral
To have this argument at all you need to know that "object-oriented programming" refers to two different things, and the man who coined the term endorsed only one of them.
What Alan Kay actually meant
Alan Kay invented the phrase. His formulation, which he repeated for decades with increasing exasperation, was that OOP means three things: messaging, local retention and protection of state, and extreme late binding of all things. Objects, in Kay's conception, were supposed to be like biological cells or like servers on a network: independent units that hide their internals completely and communicate only by sending messages. He said, in so many words, that he regretted calling it "object-oriented" because everyone fixated on the objects, when "the big idea is messaging."
Classes are not in that list. Inheritance is not in that list. Taxonomies of AbstractBaseHandlerFactory extending BaseHandlerFactory extending HandlerFactoryImpl are very much not in that list.
The bait-and-switch, and the punchline for agent builders
What happened next is one of the great bait-and-switches in the history of the field. C++ and then Java took the word "object-oriented," kept the syntax of classes, and discarded the messaging philosophy almost entirely. Mainstream OOP became class-oriented programming: model your domain as a type hierarchy, distribute your mutable state across instances, and bind behavior to data at compile time through inheritance. This is the OOP you learned, the OOP of enterprise Java, the OOP of the God class. It won the marketing war so thoroughly that Kay's version barely registers as OOP at all anymore.
Kay's original vision, the one that lost, looks almost exactly like a modern agent system. Independent encapsulated units. Total state hiding. Communication exclusively through messages. Late binding of everything, because you don't know at design time what the other party will say. An agent harness, with its agents holding private context and exchanging messages through defined channels, is closer to what Kay meant by "object-oriented" than 95 percent of the Java ever written. You are not rejecting OOP. You are rejecting a fork of it that stole the name.
You are not rejecting OOP. You are rejecting a fork of it that stole the name.
| Property | Kay's OOP | Class-Oriented Fork |
|---|---|---|
| Core mechanism | Messaging between independent units | Inheritance hierarchies binding behavior to data |
| State model | Completely hidden, per-object | Distributed across instances, shared via inheritance |
| Binding time | Extreme late binding of all things | Compile-time through class hierarchy |
III. Composition over inheritance: the call is coming from inside the house
The strongest evidence against inheritance-heavy design comes from the most canonical OOP text ever published, not from FP partisans.
The canon's own verdict
The Gang of Four's Design Patterns (1994), the book that sits on the shelf of every engineer who lauds OOP at you, states as one of its two foundational principles: "Favor object composition over class inheritance." That's not a hostile reading. It's on the early pages, stated flat. And if you actually read the patterns in that book instead of just citing it: Strategy, Decorator, Composite, Observer, Bridge. Half the catalog consists of elaborate machinery for avoiding inheritance. The most celebrated OOP book ever written is substantially a survival manual for escaping OOP's signature feature.
Why did they write that? Because by 1994, people who had spent a decade building large class hierarchies had already learned what you learned more cheaply by intuition. Inheritance is the strongest coupling relationship a language can express. A subclass is welded to its parent's implementation, not just its interface. Change the base class and you ripple through every descendant, including the ones written by the guy who left two years ago, including the ones that overrode a method you didn't expect anyone to override. The hierarchy that felt like elegant taxonomy on day one becomes load-bearing concrete by year three. This is the fragile base class problem, and it has been documented, named, and lamented for thirty years by the very community that keeps teaching inheritance in week two of every CS curriculum.
The God class, the terminal stage
The God class is the terminal stage of this disease. It begins reasonably: a Player class, a Vehicle class, an Agent class. Then requirements arrive, as they do, orthogonally to your taxonomy. Some vehicles fly. Some players are AI-controlled. Some agents need persistence and some don't. A tree can only branch along one dimension at a time, and reality is not a tree. Reality is a matrix. So you start pushing shared behavior up the hierarchy, because that's where shared things go, and the base class accumulates, and accumulates, and ten years later there's a class with 4,000 lines and 60 instance variables that everything inherits from and nobody understands, and modifying it requires a meeting. Your brain looked at this pattern early and said "no." Your brain was right. The GoF agreed with your brain in 1994. The seniors citing the GoF at you have, in many cases, not recently read the GoF.
A tree can only branch along one dimension at a time, and reality is not a tree.
Composition's opposite bet
Composition takes the opposite bet. Instead of asking "what is this thing," which forces you to pick one axis of classification and marry it, you ask "what does this thing have, and what can it do." Capabilities become parts. Parts combine freely. The matrix of real-world requirements gets represented as a matrix instead of being crammed into a tree. None of this is new and none of it is FP propaganda. It is the orthodox conclusion of OOP's own elders. The industry just has a forty-year habit of ignoring its own findings, which, if you've worked in finance, will not surprise you. Everyone knows the literature says active managers underperform after fees. The literature has minimal effect on behavior. Same energy.
IV. ECS: composition taken to its logical conclusion, with receipts
Entity-Component-System is what happens when you take "favor composition over inheritance" and refuse to stop early. The architecture has three parts, and the discipline is in how little each part is allowed to be.
The three parts, kept deliberately small
An entity is an ID. Not an object with an ID. An ID. A number. It has no behavior, no data, no methods, no opinions. A component is pure data attached to an entity: a Position, a Velocity, an Inventory, a PowerConsumer. No methods. Plain old data. A system is a function that runs over every entity possessing a particular combination of components: the movement system grabs everything with Position and Velocity and advances it; the power system grabs everything with PowerConsumer and drains the grid. Entities are rows, components are columns, systems are queries. A game tick is a batch job. ECS is your home paradigm wearing a different jersey.
Entities are rows, components are columns, systems are queries.
Two independent reasons it wins
There are two independent reasons ECS wins in large simulations.
The first is the expressiveness argument, which is the composition argument from the previous section. When an entity is just a bag of components, the taxonomy problem evaporates. A flying electric vehicle that's also a shop? Attach Flying, Electric, Vehicle, Shopfront. No diamond inheritance, no refactoring the hierarchy, no meeting. New entity types become data, not code.
The second is the mechanical sympathy argument, and this one is about silicon, not philosophy. Class-based OOP scatters objects across the heap, each one a grab-bag of hot and cold fields, accessed through pointers, processed through virtual dispatch. Your CPU hates every part of that sentence. Cache lines get filled with fields you don't need; the prefetcher can't predict pointer chases; the branch predictor chokes on virtual calls. ECS stores each component type in contiguous arrays, so a system ploughing through 200,000 positions is doing exactly the linear, predictable, SIMD-friendly memory access that modern hardware was built to inhale. The performance difference is not 10 percent. On the right workload it's an order of magnitude. This is the same reason columnar beats row-oriented for analytics, the same reason vectorized beats iterative in your pipelines, the same reason your GPU work felt familiar. Data-oriented design is the unifying idea; ECS is its game-engine expression.
The receipts, graded
Now, the games, and here I'll be more careful than the average Hacker News commenter. Your instinct that nobody is "raw-dogging OOP" through a million-entity factory simulation is correct, but the evidence comes in grades. Cities: Skylines 2 is the clean documented case: it is built on Unity DOTS, whose centerpiece is an explicit, formally documented ECS, and the developers have publicly discussed leaning on it for the core simulation precisely because the first game's traditional MonoBehaviour approach hit a wall. Factorio is closed-source; what's publicly known from the developer blogs is a relentlessly data-oriented C++ engine, prototype-driven entity definitions, flat arrays and tight loops, which is the ECS mindset without a confirmed textbook ECS implementation. Dyson Sphere Program is Unity, component-composed and aggressively data-oriented in its belt and logistics code per the modding community's decompilations, again hybrid rather than pure. So the defensible sentence, the one you can say to a principal engineer without getting picked apart, is: large-scale simulations converge on data-oriented, composition-heavy architectures because deep inheritance hierarchies fail at both expressiveness and throughput, and where we can see the receipts (Unity DOTS, and open ECS libraries like EnTT and Flecs), that's exactly what's there. Hedged correctly, the claim is stronger, not weaker.
V. The steelman: where objects earn their keep
Let's take the senior engineers seriously, because hidden inside their vague warning is a precise position, and the precise position is correct.
When a thoughtful senior says "be careful, you still need OOP," they are usually not defending inheritance taxonomies. Nobody who has been doing this for twenty years defends the God class; they've all been mauled by one. What they're defending, mostly without articulating it, is this: some things in a real system genuinely are stateful resources with lifecycles, and an object is the cleanest container civilization has invented for a stateful resource with a lifecycle.
Stateful resources with lifecycles
Think about what your harness actually touches. Database connection pools. A Redis client. A headless browser. A Docker sandbox running untrusted agent-generated code. A websocket to a model provider. A long-running agent session with accumulated context. Every one of these has the same shape: it must be initialized (auth, config, handshake), it holds genuine internal state that is not incidental but essential (the connection IS state), and it must be torn down correctly or you leak processes, file handles, sockets, and money. The C++ world calls the disciplined version of this RAII: tie the resource's life to the object's life, and cleanup becomes structurally guaranteed instead of something every call site has to remember. Python's context managers are the same idea in pajamas. with Sandbox(root) as sb: and the orphan-process problem is handled by construction, not by vigilance.

Could you model all this functionally? In principle, sure. Thread the state explicitly, or wrap it in effect monads, or push it into a runtime the way Haskell does. In a mainstream language, on a mixed-skill team, what you actually produce by doing that is a hand-rolled object system with worse ergonomics and no IDE support, built by someone who will leave, maintained by someone who will curse them. The senior engineers have watched this movie.
The second reason purists lose
There's a second, less philosophical reason, and pretending it doesn't exist is how purists lose: the ecosystem is object-shaped. Pydantic models are classes, and they're classes for good reasons: validators, defaults, schema generation, autocomplete. PydanticAI hands you an Agent class. LangGraph hands you graph and node abstractions with class-flavored APIs. Every SDK for every vector database, observability platform, and cloud service hands you a client object. You can rage against this or you can notice that client. followed by autocomplete is a genuinely good discovery interface for a teammate who joined Tuesday, and that an API designed as forty free-floating functions is not. Fighting the ecosystem's grain costs you integration speed and onboarding speed and buys you ideological purity, which compounds at zero percent.

So the scorecard reads like this. Inheritance as a modeling strategy: discredited by its own inventors' literature, avoid except for shallow, stable, framework-mandated cases. Objects as containers for stateful resources with lifecycles: genuinely the right tool, use them without guilt. The trick, and it is the entire trick, is that these two things got sold under one brand name, and the brand confusion is what generates the whole tedious debate. You don't need a side. You need a sorting rule. The next section is the sorting rule.
| Use case | Verdict |
|---|---|
| Inheritance as modeling | Discredited by OOP's own canonical literature; avoid except for shallow, stable, framework-mandated cases |
| Objects as resource containers | Genuinely the right tool for stateful resources with lifecycles; use without guilt |
VI. Functional core, imperative shell: the consensus nobody announces
In 2012 Gary Bernhardt gave a talk called "Boundaries" that quietly became the closest thing this debate has to a settlement. The pattern it describes goes by "functional core, imperative shell," and once you see it you will notice that every serious system you admire is built this way, whether or not its authors have heard the phrase.
The rule and its economics
The rule: all decisions live in a core of pure functions operating on immutable values. All dependencies, all I/O, all state mutation, all of the world's mess lives in a thin shell around that core. The core decides; the shell executes. The core is where the branching logic and the complexity live, and because it's pure, you can test it with plain assertions, no mocks, no fixtures, no network. The shell has almost no logic of its own; it's a dumb chauffeur that carries data to the core and carries the core's decisions back out to the world. Bugs concentrate where decisions are; decisions are now in the cheap-to-test part. This is not aesthetics. It's an economic argument about where to put your defect surface.
The harness mapping
Map it onto your harness and the fit is almost embarrassing. Functional core: prompt construction, model-output parsing, tool selection, routing decisions, state reducers that fold a new event into the conversation state. All of it is data-in, data-out. Given this context and this model response, what happens next? That's a pure function, and it's also the part of your harness where every bug that matters will live. Imperative shell: the actual API call to the provider, the sandbox process, the filesystem, the vector store, the Postgres write. Note that the shell is exactly where we just agreed objects earn their keep. The sorting rule falls out by itself: FP for decisions, objects for resources. The paradigm war ends not in victory but in jurisdiction.
FP for decisions, objects for resources.
You can see this pattern load-bearing in production agent frameworks right now. LangGraph's whole state model is reducers: nodes return updates, reducer functions fold updates into state, state flows through channels. That is functional programming in the trench coat of a Python framework. Anthropic's published guidance on harnesses for long-running agents has the agent externalize its state into a git repo and a progress file, so a fresh session reconstructs context by replaying durable artifacts. That is event sourcing, the FP answer to persistence, wearing a trench coat made of files. The industry converged on these shapes under pressure from the actual problem. Nobody's ideology survived contact with production; this pattern did.
Actors, Kay's objects, and your agents
Scale the pattern up, Bernhardt says: a big system becomes many small functional cores, each wrapped in its own imperative shell, communicating by messages. Then he points out that he has just described the actor model, the architecture Erlang has run telecom switches on since the eighties. Isolated stateful units, no shared memory, message passing only, supervision trees for failure. Now hold that next to Section II. Independent units, hidden state, communicating exclusively through messages, late-bound. That is Alan Kay's definition of object-oriented programming. The functional-core-imperative-shell endgame and the original meaning of OOP are the same architecture, arrived at from opposite shores. And both of them are, structurally, a description of a multi-agent harness. Your agents are actors. Your actors are Kay's objects. Your objects' internals are pure functions. The paradigms were never enemies; they were partial views of one design that this industry keeps rediscovering whenever the systems get big enough and concurrent enough to punish anything else.
VII. Hexagonal architecture without the class ceremony
You already run hexagonal architecture, so I'll skip the sales pitch and go straight to the assumption that ports require interfaces, and interfaces require classes, and therefore the FP guy has to dress up in OOP costume at every boundary.
Ports as function signatures
Alistair Cockburn's actual formulation of ports-and-adapters never mandated classes. A port is a contract: a statement of what the core needs from the outside world (a driven port: "I need to persist this," "I need to call a model") or what the outside world may ask of the core (a driving port: "handle this request"). In Java, contracts get spelled interface because that's the only word Java knows. In a functional codebase the same contract is spelled as a function signature, and the whole pattern gets lighter. Mark Seemann has a piece bluntly titled "Functional architecture is Ports and Adapters" arguing that disciplined FP doesn't merely permit this architecture, it produces it by default, because purity physically forces I/O to the edges. The hexagon isn't something you impose on FP code. It's what FP code looks like when you draw it.
The hexagon isn't something you impose on FP code. It's what FP code looks like when you draw it.
Concretely, a port becomes a function type, and an adapter becomes any function matching it. Dependency injection, stripped of the framework mysticism that grew around it in the Spring years, is just passing functions as arguments:
# Ports: contracts, written as function shapes (Protocols if you want mypy's blessing)
CallModel = Callable[[ModelRequest], ModelResponse]
RunTool = Callable[[ToolCall], ToolResult]
PersistEvent = Callable[[Event], None]
# Core: pure decisions, blind to infrastructure
def step(state: AgentState, call_model: CallModel, run_tool: RunTool) -> tuple[AgentState, list[Event]]:
...When the core needs several capabilities, bundle them in a frozen dataclass and inject the bundle; a record of functions is a port collection without an inheritance tree in sight. And notice what testing becomes: a fake adapter is a lambda. Not a MockToolRunnerFactoryBean configured across forty lines, a lambda, written inline in the test, readable in one glance.
The composition root, where the worlds shake hands
The composition root, the one place where everything is wired together, is where the two worlds shake hands. Your adapters wrap object-shaped SDKs, because Section V was right and the ecosystem is object-shaped: somewhere a Pydantic client and a connection pool and a sandbox object get constructed, managed, and torn down. But each one gets exposed inward as a plain function satisfying a port. Objects at the rim, functions in the core, and the hexagon's boundary is precisely the line where one becomes the other. That's the entire architecture. It fits on an index card, and it resolves a debate people have been having for forty years.
VIII. The IR thesis: build the harness like a compiler
Now the LLVM material, because your instinct here is the strongest card in your hand and you should know its formal name.
The M×N problem
The classical problem in compiler engineering: M source languages, N target architectures. Build a dedicated compiler for each pair and you owe the world M times N compilers. Twenty languages, fifteen targets, three hundred compilers, game over. LLVM's answer is the intermediate representation: every frontend (Clang, rustc, Swift) compiles to LLVM IR, every backend compiles from IR to its target, and the bill collapses from M×N to M+N. This is "the M×N problem," and the IR move is one of the highest-leverage structural ideas in all of software, because it shows up anywhere many producers must reach many consumers. Protocols are IRs. SQL is an IR between query writers and execution engines. Arrow is an IR between data tools. Once you have the lens, you see it everywhere, and you should, because it's the same lever every time: stop multiplying, start adding.
Stop multiplying, start adding.
Your harness through the lens
Look at your harness through this lens. On the frontend side, the M: model providers, each with their own API shape, tool-calling format, and streaming quirks, plus the agent frameworks, plus your own prompt formats, and all of it churning quarterly. On the backend side, the N: execution targets. Sandboxes, browsers, MCP servers, databases, sub-agents, human approval gates. If your orchestration logic speaks directly to providers and directly to executors, every piece of intelligence in your system is welded to vendor surfaces on both sides, and you are personally paying the M×N integration tax forever, with interest, in a market where the vendors mutate faster than you can amortize the integration work.
The compiler-shaped harness refuses both weldings. Define your own representation of the things that matter: a conversation, a tool call, a plan step, an observation, an event. Frontend adapters translate each provider's dialect into your representation. Backend adapters translate your representation into each executor's dialect. The core, the part that holds everything you actually know about orchestrating agents, speaks only IR. Now a new model provider costs one frontend adapter. A new tool runtime costs one backend adapter. Your routing logic, your retry policy, your evals, your replay tooling, all of it works unchanged across every combination, because none of it ever learned a vendor's name. In a domain with no settled standards, where every quarter brings a new API to integrate and a new framework to evaluate, the IR is how you convert vendor churn from an existential threat into a line item.
One design, seven names
And here the previous seven sections converge, the same idea at maximum altitude. The IR is immutable data; transformations over it are pure functions: that's your functional core. The adapters at both rims wrap stateful vendor SDKs in objects with lifecycles: that's Section V, objects earning their keep at the boundary. Frontends and backends are ports and adapters: that's the hexagon, with the IR as the contract language every port speaks. Events in IR form, persisted in order, give you event sourcing, replayable sessions, and evals against recorded reality. Entities-as-data processed by systems-as-functions: the ECS sensibility, generalized beyond games. One design, seven names. The compiler people just got there first and wrote the best documentation.
IX. Closing the memo: the scorecard
Howard Marks likes to say that you can't predict, but you can prepare. In architecture the equivalent is: you can't know which framework wins, but you can position so it doesn't matter. So, positions, stated plainly so you can deploy them in the next conversation with a principal who raises an eyebrow.
The four positions
On OOP: the word names two different things. Inheritance-as-modeling is discredited by OOP's own canonical literature; the GoF said "favor composition over inheritance" in 1994 and the industry's most experienced practitioners have agreed ever since, whether or not they've noticed. Objects-as-resource-containers are correct engineering, and you should use them freely at your system's edges without feeling like you've compromised anything. When a senior says "you still need OOP," agree, specifically and precisely: "at the shell, for stateful resources with lifecycles, absolutely; for domain modeling I keep the core pure." You'll have ended the argument by drawing the jurisdictional line they were gesturing at all along.
On FP: it is the core rather than the opposing team. Decisions as pure functions over immutable data, state evolution as reducers, persistence as events. Your data engineering formation is not a limitation you're compensating for. It is, for this particular decade and this particular problem, the correct prior arrived at early.
On ECS: keep it as your mental model for any system with many entities and orthogonal behaviors, cite Cities: Skylines 2 on Unity DOTS as the documented case, describe Factorio and Dyson Sphere Program as data-oriented and composition-heavy rather than confirmed-pure-ECS.
On the harness: build it like LLVM. Own your intermediate representation. Adapt vendors at the rim, keep intelligence in the core, persist events, replay everything. M+N, not M×N.
The meta-lesson
The paradigm war was never a war; it was a jurisdictional dispute that got marketed as a holy one, and the people who profit from holy wars are rarely the people fighting them. Kay's objects, Bernhardt's cores and shells, Cockburn's hexagons, Erlang's actors, the game industry's ECS, and LLVM's IR are six communities' independent discoveries of the same shape: pure decisions in the middle, managed state at the edges, messages in between, and a common representation so the pieces can change without the whole thing caring. You found that shape through GPU code and data pipelines instead of through Smalltalk and Erlang. Fine. The mountain has many trails. The view from the top is identical, and the people warning you to be careful on the way up are, it turns out, describing the same summit from a different face.
Be skeptical of anyone selling certainty about paradigms, including me. But the weight of evidence, the convergence of six independent traditions, and the specific demands of agent systems all point the same direction. You're not behind on OOP. You're early on the synthesis.
You're not behind on OOP. You're early on the synthesis.
Sources worth your time: Gamma, Helm, Johnson, Vlissides, "Design Patterns" (1994), p. 20, for composition over inheritance from the source. Alan Kay's 2003 email defining OOP as messaging, widely archived. Gary Bernhardt, "Boundaries" (2012). Mark Seemann, "Functional architecture is Ports and Adapters" (2016). Robert Nystrom, "Game Programming Patterns," the Component chapter, for the inheritance-to-composition migration in game engines. Unity DOTS/ECS documentation for the Cities: Skylines 2 substrate. The LLVM project's own architecture docs for the M×N argument. Anthropic's "Effective harnesses for long-running agents" for state-as-durable-artifacts in production harnesses.
The 1994 Design Patterns book lies open to its second founding principle: favor object composition over class inheritance. Around it, the catalog's celebrated patterns, Strategy, Decorator, Composite, Observer, Bridge, are drawn as escape hatches cut into an inheritance tree, each one a documented route out of a hierarchy that has hardened into load-bearing concrete. A timeline runs from 1994 to now, thirty years of curricula teaching inheritance in week two while the field's own canon kept the exits marked. The book every inheritance defender cites is substantially a survival manual for avoiding inheritance; citing it and reading it are different acts.
Three million-entity simulation games, graded on what their engines verifiably run. Cities: Skylines 2 carries a full check: built on Unity DOTS, a formally documented entity-component-system, adopted after the first game's traditional object approach hit a wall. Factorio and Dyson Sphere Program carry half-checks: relentlessly data-oriented and composition-heavy by every public account, without a confirmed textbook ECS underneath. Large simulations converge on data-oriented, composition-heavy architecture because deep inheritance hierarchies fail at both expressiveness and throughput. A claim hedged to exactly what the receipts support survives expert scrutiny; confident overreach gets picked apart by the first principal engineer who reads it.
Four diagrams at the corners of one frame: Gary Bernhardt's functional cores in imperative shells, scaled up until the cores talk by message; Erlang's actors, the isolated stateful units that ran telecom switches for decades; Alan Kay's original objects, cells that hide their internals and communicate only through messages; and a modern multi-agent harness, agents holding private context and exchanging messages through defined channels. Fold lines bring all four onto a single center card, and the geometry matches at every fold. Four communities, decades apart, starting from different problems, drew one architecture. The paradigm war was a naming dispute over a design everyone kept rediscovering.
Four position cards written to be carried into a real argument. On OOP: agree with the senior engineer, precisely, at the shell, for stateful resources with lifecycles, while domain modeling stays in a pure core. On FP: pure functions over immutable data are that core, the place every consequential bug concentrates and the place testing is cheapest. On ECS: the working mental model for any system with many entities and orthogonal behaviors. On the harness: build it like LLVM, own the intermediate representation, adapt vendors at the rim, pay M plus N instead of M times N. A position stated at exactly the strength its evidence supports ends arguments; a position overstated restarts them.