I used to believe this, but after working at a successful SaaS I have come to believe that correctness and unambiguity are not entirely necessary for successful software products.
It was a very sad realization that systems can be flaky if there is enough support people to solve edge case problems. That features can be delivered while breaking other features as long as enough users don't run into the middle of that venn diagram, etc.
Fact is it always comes down to economics, your software can afford to be as broken and unpredictable as your users will still be willing to pay money for it.
The overhead will get absurd, you'll end up with 10x or more increase in engineers working on the system, all of them making slow progress while spending 90% time debugging, researching, or writing docs and holding meetings to work out this week's shared understanding of underlying domain semantics - but progress they will make, system will be kept running, and new features will be added.
If the system is valuable enough for the company, the economic math actually adds up. I've seen at least one such case personally - a system that survived decades and multiple attempts at redoing it from scratch, and keeps going strong, fueled by massive amount of people-hours spent on meetings.
Adding AI to the mix today mostly just shifts individual time balance towards more meetings (Amdahl's Law meets Parkinson's law). But ironically, the existence of such systems, and the points made in the article, actually reinforce the point of AI being key to, if not improving it, then at least keeping this going: it'll help shorten the time to re-establish consensus on current global semantics, and update code at scale to stay consistent.
[0] Distributed Systems Programming Has Stalled: https://news.ycombinator.com/item?id=43195702
[1] Choreographic Programming: https://en.wikipedia.org/wiki/Choreographic_programming
In my opinion, a system that has been stable for years isn't 'mature' in a good sense. An exceptional system is one that can still change after many years in production.
I believe this is almost impossible to achieve for enterprise software, because nobody has incentive to make the (huge) investment into longterm maintainability and changeability.
For me, consistent systematic naming and prefixes/suffixes to make names unique are a hint that a person is thinking about this or has experience with maintaining old systems. This has a huge effect on how well you can search, analyze, find usages, understand, replace, change.
Obviously a lot of this you can piece together today, in fact Snowflake itself does a lot of it. But the other part of the article makes me think they understand the even harder part of the problem in modern enterprises, which is that nobody has a clear view of the model they're operating under, and how it interacts with parts of the business. It takes insane foresight and discipline to keep these things coherent, and the moment you are trying to integrate new acquisitions with different models you're in a world of pain. If you can create a layer to make all of this explicit - the models, the responsibilities, the interactions, and the incompatibilities that may already exist, then mediate the chaos with some sort of AI handholding layer (because domain experts and disciplined engineers aren't always going to be around to resolve ambiguities), then you can solve both a huge technical problem but a much more complicated ecological one.
Anyway, whatever they're working on, I think this is the exact area you should focus on if you want to transform modern enterprise data stacks. Throwing AI at existing heterogenous systems and complex tech stacks might work, but building from scratch on a system that enforces cohesion while maintaining agility feels like it's going to win out in the end. Excited to see what they come up with!
I'm looking forward to whatever these people come up with, because I believe they do understand the problem, which is the best starting position you can have.
We lucked into filesystems that have open structures (even if the data is opaque). Perhaps we should be pushing for "in-memory filesystems" as a default way of storing runtime data, for example.
[1] https://redplanetlabs.com/programming-model
> What is Rama? Rama is a platform for building distributed backends as single programs. Instead of stitching together databases, queues, caches, and stream processors, you write one application that handles event ingestion, processing, and storage.
It was a very productive way to produce most software. But as soon as you want to do something off-piste, you pay the entire productivity penalty.
I believe there are several ways achieve that analogy today, even though the technology we have access to (and our own demands) has exponentially grown in complexity. I am happy to see more people thinking about it.
[Side track: I am personally not a fan of "break it up into many tiny systems" (microservices, etc) since it removes that agility of logic/state moving around the system. I just see an attempt to codify the analog of a very large human organization.]
Now that AI lets a single person (and in some cases, no person at all!) write several orders of magnitude more code than they would possibly have been able to, the requirements of our systems will change too, and our old ways of working is cracking at the seams. In a way we're perhaps building up a whole new foundation, sending our AIs to run 50-year-old terminal commands. Maybe that's all we needed all along, but I do find it strange that AI is forced to work within a highly fragmented system, where 95%, if not 99%, of all startups that write code with AI while hiding it from the user, are essentially following the recipe of: (1) launch VM (2) tell AI to install Next.js and good luck.
I too have a horse in this race and have come to similar conclusions as the article: there is a way to create primitives on top of bare metal that work really well for small and large applications alike, and let you express what you really wanted across compute/memory/network. And I believe that with AI we can go back to first principles and rethink how we do things, because this time the technology is not just for groups of humans. I find this really exciting!
I think anything that can change this has to be simple enough that it'd be more effective to just explain the system and implement it, than wax about the general outline of part of the problem. Especially since the real target audience for an initial release by necessity needs to understand it.
There are some big leaps we could make with having code be more flat. Things like having the frontend and backend handler in the same file under the same compiler/type checker. But somebody will want to interact with a system outside of the 'known-world' and then you're writing bindings and https://xkcd.com/927/
At the end of the day I think the core tension is that once the speed of light is noticeable to your usecase things become distributed, which creates the desire for separate rate-of-change. I'm not sure what would 'solve' that.
AI will be a plus, for the fact that a single team can be in charge of more of the parts leading to a more coherent whole.
Hope OP builds some nice tools, but I've seen too many of these attempts fail to get excited about "i think we found it".
Which is tantamount to waving one's hands about and saying there's "New magic!(tm)"
... while standing next to a pile of discarded old magic that didn't work out.
This blog post says nothing about what makes Cambra's approach unique and likely to succeed; it is just a list of (valid) complaints about the status quo.
I'm guessing they want to build a "cathedral" instead of the current "bazaar" of components, perhaps like Heroku or Terraform, but "better"? I wish them luck! They're going to need it...
[Infinite screaming]
Management and sales may not appreciate good software design and good code, the next developer that has to work on system will.
In your example even as the interface for those products is unstable (UI that changes all the time, slightly broken API), those products are coded in a language like C++ or Java, which benefit from compiler error checking. The seams where it connects with other systems is where they're unstable. That's the point of this blog post.
In practice, most of the complexity comes exactly from what’s described here: every system has a rich internal model, but the moment data crosses a boundary, everything degrades into strings, schemas, and implicit contracts.
You end up rebuilding semantics over and over again (validation, mapping, enrichment), and a lot of failures only show up at runtime.
I’m skeptical about “one model to rule them all”, but I strongly agree that losing semantics at system boundaries is the core problem.
For example, say I develop some object (scene) in Godot Engine. It interacts with the environment using physics simulation, renders 3D graphics to the screen with some shaders and textures, and plays back audio from its 3D location.
I can send this scene to some other user of Godot, and it will naturally just work in their project, including colliding with their objects, rendering in their viewport (including lighting and effects), and the player will hear the location of the object spatially.
Of course there is much more you can do in Godot, too: network requests, event-driven updates, localization, cross-platform inputs, the list goes on. And all of these compose and scale in a manageable way as projects grow.
Plus the engine provides a common data and behavior backbone that makes it possible for a single project to have code in C++, C#, GDScript, and even other languages simultaneously (all of these languages talk with the engine, and the engine exposes state and behaviors to each language's APIs).
In fact, I've been thinking about making a Godot-inspired (or perhaps even powered) business application framework because it's just such a productive way of building complex logic and behavior in a way that is still easy to change and maintain.
So I imagine if Cambra can bring a similar level of composability for web & data software, it could dramatically improve the development speed and quality of complex applications.
You see even on this thread people begging for one single standard.
What actually happens with that one single standard?
- Behind it, you have a shittload of people implicitly optimizing for the general use case and hiding all the said complexity for you
- No need to worry about [semantic conflict](https://www.sigbus.info/worse-is-better)
Once you have centralization, "composition" is not so hard. You get to define all your edge cases, define how you see the real world. Everybody doesn't have their own way of doing things, you have only one way of doing things.
Of course, then comes the extension of the software. People will see the world differently. And we have not algorithmically figured out how domains themselves evolve. The centralization abstraction breaks because people disagree and have different use cases.
I don't see how you get around this fundamental limitation. Are you going to impose yet another secret standard on everybody to get the interoperability you want? If you had full control over the world, yes, things are easy.
I'm not saying this as a diss. I truly do believe centralization works. AWS? Palantir? Building the largest centralized platforms in history and having everybody go through your tooling, when executed carefully, is a dummy effective strategy. In the past, monopolies were effectively this too (though I'd say buying steel is much different than "buying" arbitrary turing-complete services to help deal with a wide variety of semantic issues, and that's what precisely makes the 'monopoly' model break in the 21st century). And hey, at least AWS is a pretty good service, insofar that it makes certain things braindead easy. Is it a "good" service, intrinsically or whatever? I don't know.
In the end most challenges for a business holding them back to better code quality are organizational, not technical.
There are many examples of models that enable coherent systems within specific domains:
- Type systems in programming languages catch many logic errors and interface misuses
- The relational model in databases enables programmers to access incredible scale and performance with minimal effort.
[...]
So coherent systems are great: everyone should just buy into whatever model will most effectively do the job. Right? Unfortunately, the listed models are all domain-specific–they don’t generalize to other contexts. And most modern internet software is not domain-specific. Modern applications typically span a wide variety of domains, including web and API serving, transaction processing, background processing, analytical processing, and telemetry. That means that trying to keep a system coherent limits what that system can ultimately do. As one implements more capabilities, application requirements push us outside of a single domain, forcing us to reach for components with a different internal model. So, bit by bit, our system fragments.
The problem of course is that type systems and databases are not meaningfully "domain-specific." They aren't technical magic bullets but they separately provide real value for the use cases of "web and API serving, transaction processing, background processing, analytical processing, and telemetry." So then why hasn't the industry settled on a specific type system? Why do database vendors (and the SQL standard) keep breaking the relational model in favor of something ad hoc and irritating?I believe the real problem is that software is symbolic and the problems it solves usually aren't. Writing an application means committing to a certain set of symbolic axioms and derivation schemas, and these are never going to encapsulate the complexity of the real world. This relates to Greenspun's 10th rule:
Any sufficiently complicated C or Fortran program contains an ad hoc, informally-specified, bug-ridden, slow implementation of half of Common Lisp.
Or in a modern context, C++/C# and managing a huge amount of configuration data with a janky JSON/XML parser, often gussied up as an "entity component system" in game development, or a "DSL" in enterprise. The entirely equivalent alternative is a huge amount of (deterministic!) compile-time code generation. Any specific symbolic system small enough to be useful to humans is eventually going to go "out of sync" with the real world. The authors hint at this with the discrepancy between SQL's type system and that of most programming languages, but this is a historical artifact. The real problem is that language designers make different tradeoffs when designing their type system, and I believe this tradeoff is essentially fundamental. Lisp is a dynamically-typed s-expression parser and Lisp programs benefit from being able to quickly and easily deal with an arbitrary tree of whatever objects. In C#/C++ you would either have to do some painful generics boilerplate (likely codegen with C#) or box everything as System.Object / void pointer and actually lose some of the type safety that Lisp provides. OTOH Idris and Lean can do heterogeneous lists and trees a little more easily, but that cost is badly paid for in compilation times, and AFAICT it'll still demand irritating "mother may I?" boilerplate to please the typechecker. There is a fundamental tradeoff that seems innate to the idea of communicating with relatively short strings of relatively few symbols.This sounds like Godel incompleteness, and it's a related idea. But this has more to do with cognition and linguistics. I wish I was able to write a little more coherently about this... I guess I should collect some references and put together a blog at some point.
> Not sure if tools and technologies can solve accidental complexity.
... and then say
> For me, consistent systematic naming and prefixes/suffixes to make names unique are a hint that a person is thinking about this or has experience with maintaining old systems. This has a huge effect on how well you can search, analyze, find usages, understand, replace, change.
I have battle scars from refactoring legacy systems where my predecessors did _not_ consistently or uniquely name things and I would not have seen it through without my sidekick, the type checker!
If this is the right framing, then the two systems aren't really competitors despite solving the same problem--they're going to appeal to fundamentally different developer sensibilities. Rama is for people who want to think like Jay Kreps or Martin Kleppmann: the event log is sacred, physical data layout is a first-class design decision, and the programmer earns the performance benefits by understanding the system deeply. Cambra (if these assumptions hold) will be for people who want to think like database users: describe what you want, let the optimizer figure out how, intervene only when necessary. These are both defensible positions and both have historical track records of working. SQL's history shows the declarative camp has ecosystem advantages once the optimizer is good enough; Kafka/Rama's history shows the log-centric camp has correctness and observability advantages for event-heavy domains.
This is true. And I get sad every time it is used as an argument not to improve tooling. It feels like sort of a self-fulfilling prophecy: an organizational problem that prevents us from investing into technical improvements... is indeed an organizational problem.
I'm not sure what point you're trying to make here. The list you're referring to is definitely a bit hand-wavy, but it also makes sense to me to read it as, for example, "today's relational databases (software) are almost perfectly aligned to the domain of relational databases (concept)". As in, MariaDB running on my Mac wraps an insane amount of complexity and smarts in a very coherent system that only exposes a handful of general concepts.
The concepts don't match what I'd like to work with in my Rails app, which makes the combination of both a "fragmented system", as the article calls it, but the database itself, the columns, tables, rows and SQL above it all, that's coherent and very powerful.
I think die-hard fans of static typing mostly fail to acknowledge this objective reality and its implications. Every time they encounter this problem again and again, they approach it as if nobody thought of this before, and didn’t develop reliable abstractions to productively work in these environments.
And while web pages can masquerade as desktop and mobile apps why wouldn't games be allowed to do the same? Godot for example can do desktop multi-window while something like Flutter (which is amazing in its own right) can't do.
But yeah, someone needs to spend time and build out UI toolkits for Godot and sadly that's not really a long weekend undertaking.
Still! It's nice to dream from time to time and imagine a reality where we can either do some generic cookie cutter UI because it's meant to get things done without much ceremony or we can pull out all the stops and plop a 3D scene to walk around the file system and shot files to delete them. And yeah, I'm aware someone did a thing like that in VS Code with Three.js (I think?)[0] and for Flutter you can do something similar in a webview inside the app proper.
Yet somehow I would rather do those things inside Godot for reasons unknown to me.
[0] Found it: https://marketplace.visualstudio.com/items?itemName=brian-nj...
You're just comparing the wrong things. Yes, when you're locked into one environment, everything works together well. The moment you interact with outside systems, all hell breaks loose. If anything, what you're saying is just that platforms should have a much larger stdlib, or abstract platform differences properly (hint: this is only doable if you're a game engine and can afford to absolutely ignore _everything_ the OS does and just concern yourself with reinventing every wheel).
Not to say there's nothing good in the games side of things: a bunch of software could benefit from accepting that some systems like a big fat central message bus and singletons can be good when handled well.
It is kind of broken now, much thanks to using web applications (and applications that are basically just wrappers for web applications), but I don't know I if want to go back.
On one side it was much easier when I could hack together a program that was good enough (since everything was the same bland grey).
On the other hand some programs certainly looks nicer today.
And it has become easier to compose logic with solutions like Maven, Nuget and the various frontend package managers.
But yes, we lost drag and drop UI development, we lost consistency and we lost a lot of UX (at least temporarily).
Uh...
> Implementing it is more than I can do alone, which is why my cofounders, Daniel Mills and Skylar Cook, and I are starting Cambra. We are developing a new kind of programming system that rethinks the traditional internet software stack on the basis of a new model.
- Tables are not relations. Tables are multisets, allowing duplicate rows, whereas relations always have a de facto primary key. SQL is fundamentally a table language, not a relational language.
- NULL values are not allowed in relations, but they are in SQL. In particular, there's nothing relational about an outer join.
In both cases they are basically unscientific kludges imposed by the demands of real databases in real problems. "NULL" points to the absence of a coherent answer to a symbolic rule, requiring ad hoc domain-specific handling. So this isn't a pedantic point: most people wouldn't want to use a database that didn't allow duplicate rows (the SQL standard committee mentioned a cash register receipt with multiple entries that don't need to be distinguished, just counted). Nullable operations are obviously practical even if they're obviously messy. Sometimes you just want the vague structure of a table, a theory that's entire structural and has no semantics whatsoever. But doing so severely complicates the nice symbolic theory of relational algebra.
That's the point I'm getting at: there isn't really a "domain" limitation for relational algebra, it's more that there's a fundamental tradeoff between "formal symbolic completeness" and "practical ability to deal with real problems." Eventually when you're dealing with real problems, practicality demands kludges.
I work at a company that thinks extremely deeply about interoperability issues and everybody is on the opposite side: it can be said that we were made as a response to xkcd 927, to try and solve the issue.
I think the company is right in that semantic decentralization with interoperability would be a good end goal, but I think just plain darwinism explains the necessity of the opposite.
Especially if it can be easy for non-technical people to build efficient UIs and databases (so they don't have to resort to spreadsheet contraptions), I think there's an opportunity here...
Not a great example of a single centralised system. The errors came from trying to write custom reconciliation code between two systems, the ERP and the bank - perfect example of the problems OP raises.
I’ve spent over a decade building data infrastructure: observability systems at Twitter, streaming data processing systems at Google, declarative data processing systems at Snowflake. From the beginning, I noticed a strange gap between the conceptual elegance of programming languages and databases, and the reality of developing and operating real systems using them. That reality is filled with tedium and stress. All of the systems I’ve ever worked on have felt brittle in one way or another: hard to change, and easy to break.
Infrastructure engineers develop paranoia around change. We invest more effort testing and deploying changes than making them. We call it maturity, but I’ve never stopped questioning it. There must be a way to delegate the tedium to our tools and focus on what attracted us to this field: brainstorming ideas, trying them out, and seeing their effects.
But what’s missing, exactly? Decades of effort by thousands of brilliant minds have gone into the field of computing, much of it directed at closing the gap between accidental and inherent complexity. Surely some major innovation in the foundation isn’t just waiting to be discovered—wouldn’t someone have found it already?
Maybe. But maybe not. The structure of modern abstractions points to a specific opportunity: the status quo forces a choice between powerful tools and general-purpose tools. This feels like a false dichotomy. There’s no reason we can’t have both—if we can find the right model.
After years of searching, I think I’ve found a model that can break out of this tradeoff. Implementing it is more than I can do alone, which is why my cofounders, Daniel Mills and Skylar Cook, and I are starting Cambra. We are developing a new kind of programming system that rethinks the traditional internet software stack on the basis of a new model. Our goal: make developing internet software feel like working on a single, coherent system, not wiring together a fragmented mess of components. In what follows, I will explain why models matter, how fragmentation undermines them, and why building multi-domain coherent systems is both possible and necessary.
Computers are magic. They let abstract concepts manifest in and affect the real world. A spreadsheet formula updates a budget, and you decide whether you can afford that shiny new thing. A routing algorithm computes the shortest path, and you arrive at your destination. A database records a transaction, and money moves between bank accounts.
Every computer program works in terms of a model: an abstract way to represent the world in simplified terms. Models allow programs to ignore the overwhelming complexity of reality, and instead focus on the parts of the world that are essential to the programmer’s goal. At its most reductive, a program is a loop that receives input, updates internal state, computes consequences, and sends output. However, that oversimplification masks a deep truth: the choice of model has a huge impact on which programs are feasible to develop and maintain. In other words, there are better models and worse models. Better models rely on intuitive, well-behaved concepts and give you useful rules about how to create programs and reason about their behavior. Great models give you superpowers. They don’t just make programs easier to read and write. They make them easier to reason about. They make it possible to create tooling that can verify, optimize, and refactor programs automatically.
So why don’t we just use great models all the time? To answer that, we need to start at the bottom. All modern computer programs ultimately work in terms of the same foundational model: bits stored in memory and instructions to manipulate them. But this model is so low-level that it’s hard to map its concepts to the familiar concepts we typically care about. In other words, given a program written in terms of bits and instructions, it’s very difficult to infer its purpose. Conversely, given an intuitive specification of a program’s effects on the real world, it’s very difficult to map this specification to a “bits and instructions” program.
To make this mapping easier, we build higher-level models atop this foundation: programming languages, operating systems, databases. Programming in terms of a higher-level model comes with a sacrifice: you give up control over how the program is “lowered” into lower-level terms. But with that loss of control comes a reduction in complexity, which is often a favorable trade. For example, garbage collection allows a programmer to not worry about deallocation, in exchange for giving up control over memory management.
Models form a partially-ordered hierarchy, with a model being higher than those it builds upon and lower than those that build upon it. But higher level models are not necessarily better suited for implementing a particular program. A better choice is a model whose concepts correspond cleanly to those of the problem domain. Working within a domain-aligned model makes it easier to convert back and forth between requirements and implementation.
Much of the value of models comes from tooling. Tooling can help us ensure correctness, improve performance, and evolve our systems over time. But tooling works in terms of a specific model, and only has leverage over the concepts in that model. For example, consider what an OS-level tool like top can tell you about your program: resource consumption, uptime, network throughput, etc. It cannot do the things that are possible for a language-level tool like gdb, which works in terms of C’s programming model.
But since tooling only helps within its model, if you frequently need to “drop down” to a lower level, you lose those benefits. The best higher-level models are ones where you rarely need to drop down. We call these models sealed: they provide an abstraction that doesn’t leak its internal details often. The modern world has many examples of ubiquitous, sealed models: it’s rare to find programs written directly in assembly, that implement their own operating system, or that manage state without a database. Once a model becomes sealed, efforts bifurcate: some people develop programs in terms of that model, others develop programs that implement it.
This is the ideal: work within a sealed, domain-aligned model, and let tooling handle the boring stuff. But what happens when the system you’re building doesn’t fit within a single model?
Modern software systems are assembled from components: databases, caches, queues, services, frontends. In principle, this is empowering—you take components off the shelf, wire them together, and have a sophisticated system.
In practice, the process is often frustrating:
So, the systems we build often end up brittle. But why? Is it a necessary consequence of building complex systems? We don’t think so. We think it happens for a specific reason.
Each component has an internal model—the concepts it uses internally. But components also need to interact with each other, and often use a different, lower-level model for those interactions. A library interacts in terms of the same model as your code. A microservice exposing an API does not.
When we build a system out of components, the model we use to reason about the system is determined by these interaction models, not the internal models. When components use a lower-level model to interact, the whole system is forced down to that level. In internet software, systems are overwhelmingly forced into what we call the “networks and operating systems” model: computers, processes, memory, network addresses, packets. These are powerful abstractions, but they’re far removed from what we actually care about. They work in terms of bytes and addresses, not objects, people, places, and actions.
For example, say we write a program and connect it to a relational database. The internal models of the program and database have clean, well-defined semantics, and they allow us to model our domain reasonably well. But the behavior of the system is not easily constrained by the semantics of either model. Instead, we have to think in terms of networks and operating systems to understand any problem that is not entirely contained to one of the components (e.g. “the server process crashed”, “the data encoding is corrupted”, “the connection was dropped”).
There’s a good reason so many components use a different interaction model than their internal model: interoperability. There are lots of models out there, with valuable components built using them. But most of those models are incompatible with each other—either because they have incompatible concepts (e.g. programming languages and databases) or because they simply don’t have the concepts we need (e.g. the programs that most programming languages produce run in a single OS process, not across multiple machines). Components with incompatible internal models cannot interact directly—they must drop down to a lower-level, common model. This is why the “networks and OSes” model is ubiquitous: it’s powerful, battle-tested, and sufficiently low-level that most components can build atop it. But achieving interoperability this way sacrifices the system-level benefits of working within a domain-aligned model.
Let’s call this kind of system a fragmented system. The distinguishing characteristic of a fragmented system is that it is assembled out of numerous components with incompatible internal models. Fragmented systems are brittle: they are hard to change and easy to break. In practice, that brittleness manifests in many ways.
Contract Mismatches
Cross-component Optimizations
Ceremony and risk around changes
Impedance Mismatches
These are symptoms. What is the underlying cause? In a fragmented system, the developer must reason about behavior in terms of a low-level interaction model. Components are not trivially composable—every time one is added or modified, the implications of that change on other parts of the system are not constrained by that component’s internal model. They are only constrained by the interaction model, which is domain-misaligned, and therefore difficult to match to the system’s requirements. The developer must carefully think through every change in those terms. Confidence that the system meets its requirements typically requires extensive validation.
Fragmented systems are intrinsically brittle. Good architecture and careful engineering can mitigate this somewhat, but without the ability to combine components within a single domain-aligned model, the tooling available to the system is fundamentally limited. The effort required to build and maintain a fragmented system scales unfavorably with its complexity.
So fragmented systems are bad. What’s the alternative? We call it a coherent system. A coherent system works entirely within a single, domain-aligned model. This constraint allows tooling to operate within that model across the whole system, creating major opportunities for verification, optimization, and automation.
There are many examples of models that enable coherent systems within specific domains:
When working entirely within models like these, the leverage of tooling is much greater. Programmers often get big boosts in their productivity, and the coherent systems they build often have better correctness and performance than comparable fragmented systems.
So coherent systems are great: everyone should just buy into whatever model will most effectively do the job. Right? Unfortunately, the listed models are all domain-specific–they don’t generalize to other contexts. And most modern internet software is not domain-specific. Modern applications typically span a wide variety of domains, including web and API serving, transaction processing, background processing, analytical processing, and telemetry. That means that trying to keep a system coherent limits what that system can ultimately do. As one implements more capabilities, application requirements push us outside of a single domain, forcing us to reach for components with a different internal model. So, bit by bit, our system fragments.
The industry’s response to this situation has been to accept fragmentation as inevitable. “Use the right tool for the job,” they say. Each domain gets its own specialized component, and programmers should wire them together. This is pragmatic advice—it reflects reality. But it also encodes a hidden assumption: that fragmentation is an acceptable cost, that we can’t do better. We reject that assumption.
But rejecting an assumption isn’t the same as proving it wrong. Is a general-purpose model, aligned with the domains of internet software, actually possible? If it were, wouldn’t one already exist? You might speculate that there’s an inherent tradeoff between generality and how domain-aligned a model can be. “Domain-aligned” and “domain-specific” sound suspiciously similar, don’t they? Empirically, we do observe such a trend. But it doesn’t seem strictly necessary. Many of the models we’ve discussed are both general-purpose and sealed. The C language exists in the stack of nearly all modern software. Linux is ubiquitous, only inappropriate in rare circumstances like hard-real-time and safety-critical systems. Relational databases are nearly as ubiquitous, with NoSQL DBs comprising only a small proportion of usage and applications only rarely needing to reach for models below that level. And sometimes the tradeoff gets pushed out entirely. Rust is more general-purpose than C, aligns with more domains, and has better tooling—without giving up performance. These innovations are rare, but they are possible.
So let’s imagine one more such innovation. If we could build a sealed model that were general-purpose across and aligned with the domains typically required to build internet software, coherent systems could be built atop it without being fenced in to a single narrow domain. This would create tremendous opportunities for tooling:
If realized, these opportunities have the potential to revolutionize the development of internet software.
This is what we’re trying to implement: a general-purpose, domain-aligned, sealed model for internet software. It’s a bet against the conventional wisdom that says “use the right tool for the job” and simply accepts the resulting fragmentation. We believe systems shouldn’t have to trade between coherence and building on general-purpose models—and that the payoff for having both is immense.
Of course, we’re not the first to attempt this. Many have tried to build general-purpose, domain-aligned models for software development. Most sacrifice domain alignment to achieve generality, which is the case we’ve focused on. Some sacrifice generality to achieve domain-alignment, such as Erlang, Smalltalk, and Prolog. The ones that achieved both failed to become sealed—they leaked too often to displace lower-level alternatives. Memorable attempts include object-oriented databases and J2EE. So why do we think we can succeed? We’ll share more about our approach in the future. For now, we’ll just say: we believe advances in programming language theory and database systems have opened a path that wasn’t available before.
That’s the core argument. But there’s a question we expect many readers are already asking: doesn’t AI change everything? Why worry about models and coherence when agents can just handle the complexity for us?
We’ve been speaking of tooling in the abstract, and using examples of traditional tooling. But the most powerful form of tooling emerged only recently in the form of agentic AI. Agents are much more flexible than previous forms of tooling, and they have tremendous potential for improving the productivity of developers. They already represent a revolution in software development.
Given this, there’s a popular narrative that AI itself is sufficient to realize the opportunities referenced above. In this narrative, code is merely a by-product, an intermediate representation of the programmer’s intent, the truth of which is captured by the prose prompts and documents that are provided to the agents. The agents will handle all of the complexity of code. We don’t need to worry about abstractions or maintainability. Those are vestigial concerns from an era that will soon be past. AI is the future, and traditional software engineering will soon be obsolete.
We believe this narrative builds on several fundamental misconceptions of software.
The first misconception is the conflation of ambiguity and abstractness. People often think of code as “low level”, when what they really mean is that it’s domain-misaligned. But this is not an inherent property of all code. It’s a property of the model being used, relative to the problem being solved. Some models are lower-level, some are higher-level. Code can be either. What code is truly about is precision: code is unambiguous, even when it’s abstract. It’s easy to conflate ambiguity and abstraction—both refer to “a single statement that could have to multiple meanings.” But the meanings of an ambiguous statement are entirely unconstrained. In contrast, the meanings of an abstract statement are tightly constrained by the semantics of its model. Prose is ambiguous, and always will be. Fluidity of meaning is core to its utility. Code is precise. Precision is core to its utility. Whether the author is human or AI may shift over time, but code will never be obviated by prose because precision will always be important when designing a complex system.
The second misconception relates to the alignment between models and domains. Even when communicating in prose, programmers and AI still need shared concepts to reason about. Models will always be important for that—they give concepts meaning. Those models must align with the domains of the problems we seek to solve. If they don’t, problems quickly become intractable. That’s what the difference between fragmented and coherent systems is all about: using a single, domain-aligned model to make problems tractable. That distinction will remain relevant, no matter who is building the system.
The third misconception relates to the value and rarity of good models. Some would argue that AI will get good enough to shield programmers from the problem of domain misalignment, negating the value of domain-aligned models. The problem with this view is that it doesn’t engage with what must be happening inside the AI for this to happen. Shielding a programmer from domain misalignment is a very hard problem, and the best way to do it is to invent a sealed, domain-aligned model, and present that new model to the programmer. An AI armed with such a model will outperform an AI without one. Inventing such models is hard, and those models are valuable once found. The pursuit and usage of sealed, domain-aligned models will continue, whether it is humans or AIs pursuing them.
With a more nuanced understanding than the popular narrative, we are led to very different conclusions. AI is powerful, yes, and it will likely keep getting more powerful. But while AI may be able to reason faster or remember more than a human, some constraints apply universally: the problem space of the universe is too large to explore by brute force. Intelligence consists in finding good models with which to understand and manipulate the universe. In software, we have access to many powerful models, but there are obvious opportunities for improvement. The discovery and implementation of useful new models will give AI superpowers, just like it does us.
There is already substantial evidence in support of this view. Look no further than agentic coding. If you unleash even our most powerful AI agents in a complex, poorly-structured codebase, it tends to make a mess. Agents are most powerful when working within narrow environments with clear rules, such as:
These are just cases of agents building coherent systems within domain-aligned models.
Naturally, the capabilities of AI systems will continue growing. Consequently, so will the extent of the domains they can excel within. But there will always be room for innovations that make AI more productive. AI makes it easier to work at higher levels, but it is not an excuse to stop innovating at the lower levels. Right now, we badly need a programming model that will enable the development of coherent internet applications. For now, AI can help, but it won’t do it for us. We have to build it ourselves.