The Shift
The Staircase
In 1972, at Bell Telephone Laboratories in Murray Hill, New Jersey, a programmer named Dennis Ritchie wanted to write a text editor.
The only language available was assembly — a notation so low-level that before Ritchie could process a single keystroke, he first had to write code to manage memory allocation, handle hardware interrupts, and control the terminal display. The text editor was the goal. The infrastructure was the obstacle.
So Ritchie did something that changed computing: he created the C programming language. C introduced procedures — named blocks of code you could call by name, pass data into, and get results from. Instead of writing fifty lines of assembly to display text on screen, you could write display_line(text, row) and the procedure handled the rest.
This seems obvious today. In 1972, it was revolutionary. It was the first answer to a question that software engineering has been asking — and re-answering — every decade since:
What is the right-sized piece to build with?
Five answers in fifty years. Procedures in the 1970s. Objects in the 1990s. Services in the 2010s. Agents in 2025. Each answer lasted about a decade. Each felt permanent. None was.
Here's what most engineers miss about this staircase. Every transition looks like it was driven by new technology — a better language, a faster protocol, a smarter framework. It wasn't. Every transition was driven by composition pain — the inability to build large systems from the current unit without everything breaking.
Procedures died because every function in a program shared the same global state. Change one variable inside calculate_tax(), and three other functions across the codebase — generate_invoice(), process_refund(), audit_trail() — silently broke. The pain was so widespread that in October 1968, forty of the world's top computer scientists — Edsger Dijkstra, Tony Hoare, Alan Perlis, Peter Naur — gathered in Garmisch, Germany for the NATO Software Engineering Conference. They gave the problem a name: the "software crisis."4NATO Software Engineering Conference, Garmisch, Oct 1968. Report PDF
Objects solved the global state problem by encapsulating data inside classes — each object owned its state, and you interacted through methods, not raw variables. But objects introduced a new pain: deep inheritance hierarchies. A PremiumCustomer extending Customer extending User extending Entity meant changing Entity could break every class in the chain. "Favor composition over inheritance" became the battle cry of the Gang of Four in 1994, after a decade of burned developers.
Services solved the deployment problem that objects couldn't: independent teams deploying, scaling, and updating their code without coordinating with every other team. But services introduced the "distributed monolith" — ten microservices that were technically in separate repos but practically coupled through shared databases, synchronous calls, and undocumented API dependencies. Change the user service, break the billing service. Sound familiar?
Each step on the staircase solved the composition pain of the previous era. And each introduced a new kind of pain that nobody anticipated until it was too late.
Agents are not exempt from this pattern.
Agents solve the integration pain that services couldn't. Instead of writing custom code for every workflow — "if this webhook fires, call that API, transform the response, write to this database" — you describe the goal in natural language and the agent figures out the steps. This is genuinely powerful. It eliminates thousands of lines of glue code.
But agents introduce a composition problem that is fundamentally different from anything software engineering has seen before. Not harder. Different in kind. The next two chapters explain what that difference is and why it changes everything about how you build.
Before you can solve the agent composition problem, you need to identify which composition pain you're currently hitting. The staircase isn't theoretical — your codebase is sitting on one of these steps right now. Most teams don't know which one until something breaks.
// LOCATE YOUR POSITION
current_unit =
composition_pain =
pain_frequency = daily | weekly | every_deploy
next_step_solves =
// If pain_frequency == "every_deploy", you've outgrown your unit.