I’ve been developing software long enough to have lived through several generations of development methodologies. I started my career when waterfall dominated, experienced the transition to Agile, watched Scrum become institutionalized, and now build software with AI coding agents as the industry starts talking about autonomous software factories.

Lately, I’ve been noticing something familiar: we’re talking about specifications again. Not just requirements or user stories, but increasingly detailed descriptions of what software should do, how it should behave, and how we’ll verify that it works correctly.
What’s interesting is that the reasons for writing detailed specifications have changed. In the waterfall era, we invested heavily in specifications partly because implementing software was expensive. Today, we’re returning to detailed specifications partly because implementation is so fast that an incomplete or incorrect specification can quickly become a substantial amount of working software.
The economics have changed, but the underlying challenge hasn’t.
We may soon be able to produce software faster than we can understand what we should produce.
Others are raising similar concerns. But I think it’s worth looking at what happened the last time we tried to organize software development around getting the specification right.
What Agile Was Supposed to Fix
Early in my career, we spent tremendous effort trying to get requirements right before development began. We wrote detailed requirements documents, developed use cases, created UML diagrams using tools like Rational Rose, and modeled systems from multiple perspectives. The idea was to understand as much as possible about what we were building before writing the code.
There were good reasons for this. Writing software was expensive, and changing it after significant development had occurred could be even more expensive. The more we could anticipate upfront, the less rework we’d encounter later. At least that was the theory.
The problem was that requirements were rarely as complete as we thought. Customers didn’t always know exactly what they needed. We didn’t always ask the right questions. And assumptions that seemed reasonable on paper sometimes fell apart when people saw the software working.
Worse, development could take so long that business priorities and customer needs had changed by the time we delivered. We could execute perfectly against the original specification and still miss the mark.
As Barry Boehm famously distinguished, there’s a difference between building the product right and building the right product.
Agile challenged the assumption that we needed to understand everything upfront. Instead, we could build something smaller, get feedback, learn, adjust, and repeat.
But one of Agile’s most important contributions often gets overlooked. It wasn’t just about shorter development cycles. It was about changing how people worked together.
The Agile Manifesto’s principles explicitly call for business people and developers to work together daily. Cross-functional teams brought product, engineering, testing, and design into a collaborative process. Engineers could challenge assumptions, ask questions, suggest alternatives, and help shape the solution. Requirements became the beginning of a conversation rather than the final word.
Of course, Agile didn’t always work that way in practice. Over time, I watched daily standups become status reports and sprint reviews turn into demonstrations of completed work, often with limited participation from business stakeholders. We began questioning whether these ceremonies were worth the time.
Sometimes that criticism was justified. But the ceremonies weren’t the point. The conversations and learning they were supposed to create were. In trying to optimize Agile for delivery, I think we sometimes lost sight of the collaboration that helped us determine what to deliver.
Many teams drifted into what John Cutler called feature factories: very good at shipping features, less focused on whether those features delivered meaningful outcomes. Across the industry, Agile teams became delivery teams, only to rediscover that we needed product teams.
That history is ironic, given how enthusiastically we’re now talking about autonomous software factories.
New York in the Wrong Hemisphere
Over the past year, I’ve spent a fair amount of time building software with AI coding tools, particularly Claude Code. One of those projects is GeoSheet, a spatial spreadsheet I’ve been building largely on my own, with Claude Code as my primary development tool.
The experience has been remarkable. Tasks that previously required significant engineering effort can sometimes be completed in a fraction of the time. But I’ve also noticed how much the result’s quality depends on how well I communicate what I want.
When I’ve worked with experienced engineers, they’ve often had context beyond the requirements themselves. They knew the customers, understood why we made previous decisions, and sometimes challenged what we asked them to build. AI agents can do some of that too, but I’ve found they’re often willing to make a reasonable assumption and keep going rather than stop and ask a question.
Sometimes those assumptions are correct. Sometimes they’re not.
It’s not surprising that we’re seeing renewed interest in specification-driven development. Frameworks such as GitHub’s Spec Kit emphasize structured specifications, implementation plans, and verification. GitHub describes an iterative process, and I’ve found that clearer requirements, constraints, and acceptance criteria make a real difference.
Having spent part of my career creating detailed requirements and UML models, I can’t help noticing the similarities. But simply adding more detail doesn’t necessarily solve the underlying problem. We tried that before.
Here’s an example from GeoSheet.
In March, I added support for Well-Known Text, or WKT, a standard format for representing geometric shapes. The complication was that the geographic data I was importing used longitude-first coordinates, while GeoSheet had traditionally used latitude-first coordinates.
The specification introduced what seemed like a reasonable heuristic to distinguish between the two. If a coordinate fell outside the range of −90 to 90, it had to represent longitude. If both coordinates could be either latitude or longitude, the system assumed GeoSheet’s existing order.
The implementation shipped with 45 new tests. Those tests included New York, Los Angeles, San Francisco, Paris, and London. But the ambiguous coordinates were tested in latitude-first order. One test explicitly confirmed that ambiguous input would default to GeoSheet’s existing convention.
That distinction mattered.
Consider New York. Its longitude is approximately −74, and its latitude is approximately 41. Both values fall within the valid latitude range. A standard longitude-first WKT polygon could therefore be interpreted in the wrong order, even though tests using New York coordinates were passing.
On April 30, about five weeks after the implementation, the problem surfaced during manual testing of a new map renderer. A New York State polygon had its coordinates swapped, placing it around East Antarctica. The testing notes described the map view being pulled into the South Atlantic while trying to frame that polygon and a correctly positioned New York point.
The eventual fix included a memorable commit message: “NY state rendered in the South Atlantic.”
The tests weren’t simply missing an example. They were confirming the wrong behavior.
I had let GeoSheet’s existing coordinate convention overrule the input format’s convention.
That’s what makes this interesting to me. I had agreement between the specification, implementation, and tests, but that agreement didn’t make the assumption correct.
When the Tests Agree With the Mistake
Imagine an autonomous software factory where multiple specialized agents collaborate to produce software. A requirements agent develops a specification. A planning agent determines how to implement it. Development agents write the code. Testing agents generate and execute tests. Review agents examine the results.
Each agent successfully performs its assigned task. The implementation passes its tests, the reviews are completed, and everything looks good. But what happens when the original understanding of the problem was wrong? Or when the agents share the same incorrect assumption?
In traditional waterfall development, work moved through sequential handoffs. Agile attempted to address the resulting misunderstandings through cross-functional collaboration and continuous feedback. Now we’re building systems that could automate much of that work.
If one of Agile’s most important contributions was creating opportunities for people to question requirements, how do we preserve that in an autonomous software factory?
Some software-factory approaches already address this concern. StrongDM’s principles, for example, emphasize end-to-end validation, holdout scenarios, and feedback from real-world behavior. They include incident replays, customer interviews, and surveys among the inputs that can help improve the system.
I think that’s encouraging. Those mechanisms can help us test more than whether the implementation agrees with the specification.
But they also bring us back to a familiar question: how do we know that our scenarios and success criteria reflect what customers actually need? And what causes us to reconsider them?
An independently maintained scenario can catch something the implementation’s own tests missed. But if the scenario and the specification share the same incorrect assumption, we still need something outside that agreement to reveal the problem.
There’s a reasonable counterargument here. If AI makes software development dramatically cheaper, does it really matter if we build something incorrectly? Can’t we just change the specification and regenerate it?
Sometimes, absolutely. One of the most exciting aspects of AI-assisted development is how quickly we can experiment, throw something away, and try again. But we have to recognize that what we’ve built is wrong before we can correct it.
If the specification, implementation, and tests share the same incorrect assumption, the system may have no reason to flag the problem. And while regenerating code may be inexpensive, discovering a mistake isn’t necessarily inexpensive. Neither is the opportunity cost of pursuing the wrong solution, exposing customers to incorrect behavior, or introducing complexity that subsequent changes must account for.
Making code cheaper to produce doesn’t necessarily make mistakes cheaper to discover.
AI coding agents can take on more of the implementation work. But we still have to understand the problem, question our assumptions, and figure out what to build.
Designing for Questions
I’ve seen that distinction play out in my own development process.
When I started building GeoSheet, I moved quickly, often going directly from an idea to implementation. Over time, I added more structure: written specifications and open questions to resolve before development. Pull requests also received automated reviews from Devin (by Cognition), separate from the Claude Code sessions used for development. More recently, I added evaluations that tested the AI assistant’s behavior from the user’s perspective.
In some ways, I found myself returning to practices that reminded me of earlier stages of my career. But the goal wasn’t to specify everything perfectly upfront. The goal was to create more opportunities to catch incorrect assumptions before they became embedded in working software.
I’ve also seen this process surface problems before implementation. While planning background operations for GeoSheet’s AI assistant, the specification raised sorting and undo as an open question. The follow-up analysis found that a partial undo could leave edited cells associated with the wrong records. Sorting, along with row inserts and deletes, was left out of that phase.
That’s the kind of conversation I want to make sure I’m involved in.
The point isn’t whether agents can exercise judgment. It’s whether our processes create the moments where that judgment gets used.
A process that rewards task completion will tend to produce completed tasks. We should also make room for discovering that a task needs to change, or that we shouldn’t be doing it at all.
Building Factories That Learn
I don’t think software factories are a bad idea. One of the most exciting possibilities is that AI could help us realize some of Agile’s original goals more fully than we ever could before.
The same speed that lets agents produce software faster could also let us put working software in front of customers sooner, test assumptions, gather feedback, and make adjustments. But only if we use that speed to accelerate learning rather than simply accelerate production.
DORA’s guidance on user-centric focus makes a similar point, explicitly warning of a feature-factory trap when AI accelerates output without sufficient attention to user needs.
If we’re not careful, we could create the most efficient feature factories we’ve ever seen.
The technology might be fundamentally different, but the organizational problem would be remarkably familiar.
I think three principles are worth carrying forward as we design these systems.
Challenge assumptions using independent evidence. If every agent relies on the same incomplete specification, adding more agents doesn’t necessarily improve the outcome. We need customer evidence, established domain conventions, production behavior, or independent evaluations that can challenge the specification itself.
Don’t just measure how much software we’re producing. How quickly are we validating assumptions? Are faster development cycles translating into better customer outcomes? Those questions matter as much as the number of tasks completed or pull requests merged.
Someone still needs to own whether we’re building the right thing. Agents can help investigate customer needs, evaluate alternatives, and recommend priorities. They can’t be accountable for whether we’re solving a problem worth solving or whether the result creates meaningful value for customers.
That doesn’t necessarily mean adding another review agent or approval step. It means designing feedback and learning into the process rather than assuming successful execution equals success.
The problem with waterfall wasn’t that we wrote detailed requirements; it was that we put too much confidence in our ability to get them right upfront, and we often discovered too late that we hadn’t. Agile helped us recognize that building software is also a learning process.
AI gives us an opportunity to dramatically accelerate that process. We can use that speed to understand problems, challenge assumptions, validate solutions, and deliver better products.
That is the type of software factory I’d want to build.
Because the real risk isn’t that software factories will fail to produce working software.
It’s that they’ll become extraordinarily good at producing exactly what we specified, even when what we specified was wrong.