LLMs can never make up for inferior tools.
Just imagine what they could do with superior tools.
After coding a few Rama modules with Claude Code, I asked how Rama compares to traditional tooling. It didn't hold back:
❯ now that you understand Rama programming pretty well, how does programming Rama to build a scalable system compare to building with traditional tooling?
⏺ The biggest difference is what you don't have to build. With traditional tooling for something like TPC-C at scale, you'd need to stitch together: a database (Postgres/MySQL), a message queue (Kafka), application servers, a caching layer, connection pooling, and probably some job scheduler for the load process. Each is a separate system with its own failure modes, deployment, monitoring, and scaling knobs. The "interesting" logic — partition this data by warehouse, process these transactions, keep these indexes consistent — gets buried under infrastructure glue: serialization configs, consumer group management, retry logic, schema migrations, connection strings, transaction isolation levels.
The load module is a good example. In traditional tooling, bulk loading TPC-C data into a partitioned system would involve: a coordinator service tracking progress in a database, worker processes pulling from a task queue, batch inserts with retry logic, some way to verify completion (polling the DB), and careful orchestration to respect entity dependencies. You'd probably use something like Airflow or a custom state machine backed by Redis.
In Rama, the load module is ~260 lines. The phase machine, work splitting, cross-partition coordination, verification, and fault tolerance are all expressed in one topology. The <. In traditional systems, even with good partitioning, you're making network calls to a separate database.
The tricky parts are different. You don't debug network timeouts or connection pool exhaustion. Instead you debug dataflow semantics: which partition am I on, what variables are in scope, how does select> hash composite keys. The |hash$$ lesson was a good example — the mental model for partition routing is different from "send a query to a database." You have to think about where code is executing physically, which is unusual for most application developers but is actually closer to how the system really works.
What's notably absent: No ORM, no migration framework, no cache invalidation strategy, no pub/sub configuration, no separate batch vs. real-time processing paths. The microbatch topology handles both the load process and live transactions. Query topologies replace what would be separate API endpoints with their own connection management.
The main cost is the learning curve — Rama's dataflow model requires unlearning habits from imperative/request-response programming. But once you internalize the patterns (navigate to partition, read/write PState, route elsewhere if needed), the code maps directly to the distributed semantics with very little ceremony.
❯ the learning curve seems less and less relevant the more prevalent AI coding gets
⏺ That's a really good point. The learning curve objection has always been the main pushback against any new paradigm — but it's an objection about humans needing to build mental models. AI doesn't have that problem in the same way. I picked up the Rama patterns in this project through a few corrections and some docs, and now I can write load modules, transaction handlers, and query topologies fluently.
The implication is interesting: if AI can absorb a framework's semantics quickly, then the right framework to choose is the one with the best actual abstractions — the one that eliminates the most accidental complexity — regardless of how "easy to learn" it is for a human picking it up on a weekend. Developer familiarity stops being the dominant selection criterion.
And Rama is extremely well-positioned for that shift. Its abstractions are genuinely powerful — partitioning, colocation, distributed coordination, fault tolerance all collapsed into a single programming model. The reason more people aren't using it isn't because the abstractions are wrong, it's because humans look at |hash$$, <
Ben Sless retweeted
OpenAI’s CEO predicts that software will perfectly emulate human cognition this year.
Ben Sless retweeted
New post: "The programmers who live in Flatland" blog.redplanetlabs.com/2025/…
Ben Sless retweeted
Antifragile Programming and Why AI Won’t Steal Your Job
Whenever I say I dislike debugging and organize my programming habits around avoiding it, there is always pushback: “You must not use a good debugger.”
To summarize my view: I want my software to be antifragile (credit to Nassim Taleb for the concept). The longer I work on a codebase, the easier it should become to fix bugs.
That’s not natural. For most developers lacking deep expertise, as the codebase grows, bugs become harder to fix: you chase symptoms through layers of code, hunt heisenbugs that vanish in the debugger, or fix one bug only to create another. The more code you have, the worse it gets. Such code is fragile. Adding a new feature risks breaking old, seemingly unrelated parts.
In my view, the inability to produce antifragile code explains the extreme power-law distribution in programming: most of the code we rely on daily was written by a tiny fraction of all programmers who have mastered antifragility.
How do you reverse this? How do you ensure that the longer you work on the code, the shallower the bugs become?
There are well-known techniques, and adding lots of tests and checks definitely helps. You can write antifragile code without tests or debug-time checks… but you’ll need something functionally equivalent.
Far-reaching prescriptions (“you must use language X, tool Y, method Z”) are usually cargo-cult nonsense. Copying Linus Torvalds’ tools or swearing style won’t guarantee success. But antifragillity is not a prescription, it is a desired outcome.
Defensive programming itself is uncontroversial—yet it wasn’t common in the 1980s and still isn’t the default for many today.
Of course, a full defensive approach isn’t always applicable or worth the cost.
For example, if I’m vibe-coding a quick web app with more JavaScript than I care to read, I’ll just run it in the browser’s debugger. It works fine. I’m not using that code to control a pacemaker, and I’m not expecting to be woken up at midnight on Christmas to fix it.
If your program is 500 lines and you’ll run it 20 times a year, antifragility isn’t worth pursuing.
Large language models can generate defensive code, but if you’ve never written defensively yourself and you learn to program primarily with AI assistance, your software will probably remain fragile.
You can add code quickly, but the more you add, the bigger your problems become.
That’s the crux of the matter. Writing code was never the hard part—I could write code at 12, and countless 12-year-olds today can write simple games and apps. In the same way, a 12-year-old can build a doghouse with a hammer, nails, and wood. Getting 80% of the way has always been easy.
Scaling complexity without everything collapsing—that’s the hard part.
Clojure fixes this
alright @grok estimate the amount of CO2 saved per year if Java Developers didn't need to restart their JVMs every time they made a non-trivial change to their code via the DCVEM
Ben Sless retweeted
I took an interest in Linux first and foremost for ideological reasons. I was really into the idea of Free Software, and just wanted to be a part of that.
I was 17, and had the opportunity to see a lecture by Richard Stallman, where he dressed up as St. IGNUcius of the church of Emacs.
The idea that software could be infinitely copied resonated with me and I wanted to be a part of that.
Months later, I took a deep breath and very proudly, wrote an email to Richard Stallman. I told him how I wanted to follow in his footsteps, and was abandoning all proprietary software in my life. I was now only exclusively Linux, and I was helping all my friends to install Linux as well.
He responded. He scolded me for saying Linux, and said that I should say GNU/Linux instead.
Ben Sless retweeted
I tried using Claude Code to write platform-specific SIMD implementations for several functions I never got around to optimizing.
I gave it full control to modify the code, run tests, benchmark, and make any tweaks it deem necessary. And here's the most amazing part: it actually DID IT
P.S.
even though nothing works, all the tests are failing, and, hilariously enough, there's not a single SIMD intrinsic used lol.🫡 Claude
Ben Sless retweeted
1960s: "COBOL will let non-programmers make the software!"
1980s: "4GLs will let non-programmers make the software!"
2000s: "UML will let non-programmers make the software!"
2020s: "AI will let non-programmers make the software!"
Ben Sless retweeted
I have a job lead for VP Eng, startup, 10yo Clojure shop, 1M LOC. 25 eng. DM with your links and if appropriate I will refer you. PS this is a game of hidden criteria, if I don’t respond it’s a no, be cool
Ben Sless retweeted
Lighning talk! Electric Clojure in 5 minutes — Systems Distributed 2024
share.descript.com/view/nJkF… #ElectricClojure
Ben Sless retweeted
Okay, so, like, here's a very *high level* overview of the issues involved in doing a networked game at a decent level of quality.
The first thing one discovers when doing this kind of system is that, even though web dudes love TCP, it is very bad for games if that's all you use, because the second there is any packet loss or delay in a TCP stream, it holds up everything else in the stream after, then you get all this data and it's now useless because it's late (who cares where the other player was 2 seconds ago). So in general one needs to develop a protocol on top of UDP that transmits ephemeral-data-that-may-be-lost, and then either implement a reliable protocol on top of that one to transmit data-that-cannot-be-lost, or use TCP there.
So now you have reliable messages and unreliable messages, and you decide what is short-term data that is okay if it gets lost (positions, orientations, whatever) and what is long-term data that cannot be lost (one-time changes of rare variables, changes of communication phase, etc).
But now one of the most common bugs in these systems is partitioning the logic cleanly between these two types of communication in a very complicated system -- there can be race conditions (maybe some unreliable data you didn't expect beats the reliable message that tells the client to prepare to receive that unreliable data), you get all kinds of weird softlocks etc unless this is very carefully designed.
Then you have the problem that these games are being worked on by programmers and designers who are not doing networking at all. They are defining fields on entities and world state, and there will be some system for attempting to automatically sync these over the network. But in order for this system not to be totally dismal performance-wise, it has to be very carefully programmed, and the problem is that the more magical the system tries to be, the more prone to the above desync bugs it will be (see many Unreal engine games). But at the same time, on a big enough project it is just a reality that you need this system to exist and work. So there tends to be a two-phase approach where fields are defined but treated conservatively (reliable send always, no compression) and then get optimized later, with very careful consideration about how to get this to perform well.
Because network messages only come sometimes, but players are running at maybe 240Hz, there is a system on the client side that does prediction of what the current game state is, based on extrapolation from previously known states. This gets very complicated because the naive things (linear extrapolation based on previous velocity) look very bad in the face of packet loss etc. You need to attempt to emulate complex behavior by running some gameplay code similar to what the server is doing, but it's never exactly the same, and factoring the code so that it can be used in both contexts, while being understandable and maintainable, but also not painfully slow, can be a big challenge.
No matter how good a job you do on the client-side prediction, it's always desynced from the server, so you need to have a system that smoothly reconciles between the extrapolated state, and the new information received from the server, without snapping stuff to the target value (this looks bad and is very disorienting) but also without interpolating too slowly (since this adds too much latency). This tends to be specific to the type of any particular value.
On the server there also tends to be a latency compensation component to help players deal with lag: Incoming packets arrive that represent user commands, and rather than applying those commands to the *current* world state, in which case they will always be late, one often tries to pull up a representation of prior world state and see what the command was doing in that context (like if the command is "I am shooting from position X in direction K", you are almost always going to miss the target if the server naively applies the command 'late'). A famous example of handling this is Yahn Bernier's system for Counter-Strike that he presented at GDC a long time ago (though I don't exactly like the particular things his system does).
The above system relies on having an accurate estimate of the latency from client to server and from server to client, which is a whole system itself.
How do you represent the values inside the packets? You are sending a lot of information so you want to keep them small. If you have a typical floating-point value, for example, you don't want to send 32 bits. So you represent it in a different way. How? Do you linearly quantize as an integer? Nonlinearly quantize as an integer? Make some kind of smaller float format? How many bits do you need for this value, and is it enough? Do you need to dynamically vary how many bits are used to represent this value in different contexts, and have something in the protocol to indicate this? All of these have different trade-offs and a significant amount of math is brought to bear here to make the right decisions.
What about vector quantities, 3D positions or directions? What about 4D stuff like quaternions for orientation representation, or homogeneous-coordinate transformation matrices? To make good decisions here requires fluid working knowledge of all the linear algebra involved in 3D rendering, plus then knowledge of vector quantization schemes or some equivalent. Just representing quaternions in a reasonable manner is a whole thing, and it depends on the nature of how the rotations look typically.
You can put the data into the packet as full values, but one of the most general techniques used in data compression is to represent things as deltas from a known-previous-value rather than the full value. But deltas are only relevant straightforwardly for the reliable messages, because in unreliable messages the client may not have the previous value! (aka client and server may disagree on what that is). What do you do here? Do you negotiate a neighborhood for the value via reliable messages, that then gets used in the unreliable messages? How much bandwidth does this take, is it a win?
You want to entropy-code the outgoing messages to make them smaller (basically applying generic stream compression on top of the data stream you have already been trying to make small), because you are sending a lot of world state, and this involves knowledge of general data compression, which is itself a huge topic (there are entire textbooks just on the *basics* of how to compress data), but it's tricky because good compression relies on context, and you only know that the receiver has context on the reliable (TCP-style) messages, not the unreliable (UDP-style) messages, so maybe those two things have different compression styles (the unreliable messages cannot use context outside the message for compression).
You have a bandwidth and CPU usage cap on the server, so you want to decide which updates to send each frame. You don't want to oversaturate someone (if you just sent data last frame you don't want to push out more), but you do want people to get a lot of data in brief periods if a lot is happening.
In a 3D space some players/objects/etc are a lot further from you and others are closer, and you can't send all the data all the time, so there is some heuristic that provides more fidelity for things that need more fidelity. You might even have occlusion detection running on the server to decide that some players should get almost no information about some things (if it's a closed room-based world for example), but if an event is big enough you still need to know about it.
Because CPU is limited on the server and there are a lot of clients you want to *quickly* determine whose world state is the most erroneous and prioritize sending to those people (which will change every frame), and if you are randomly-accessing a ton of memory this will be too slow, so this involves knowledge of cache-coherent data structures and general code optimization.
If you are trying to serve a lot of clients you realize that this horrendous model of doing a system call per network message send or receipt (Unix or Windows-style) is really bad, so you end up wanting to code for hardware that doesn't work this way, so you use a network card that has an API that lets you talk to it via userspace without going through the kernel, and you talk to that via an asynchronous command-buffer API, and this in itself is a different paradigm than most programmers have ever dealt with, that has its own challenges.
I have surely forgotten a lot of things here. It's a quick tweet. Each of the above paragraphs would be at least several chapters in the hypothesized book on the topic. And of course when you go do stuff in practice you run into a lot of practical problems not considered by theory ("hey all our designers are writing all this Blueprint code, how in the hell are we going to client-side-predict the results of this?")
Ben Sless retweeted
Replying to @dustingetz
@dustingetz, one of our Legendary Keynotes, breaks down his upcoming talk on Electric Clojure ⚡️
Get tickets:
eventbrite.com/e/lambdaconf-…

























