Complexity Kills the Cat
AI agents don't get lost because a spec is too big. They get lost because it's tangled, so I built a tool that untangles specs.
As an experiment, I had my 13 year old son sit at my computer and instruct AI to build him an Asteroids game. It immediately understood the task, and he proceeded to give it more and more detailed refinements until the game was more fine-tuned to be what he wanted it to be. The AI coding agent did just what it was told to do and took no detours, went down no rabbit holes, and didn’t get lost once.
When Fable 5 came out, I decided to test it by seeing how far it could get on a dream project of mine: a starship bridge simulator game inspired by Artemis SBS and an old game called Star Control II. After writing a spec for most of the day, it proceeded to build, and ultimately I got an almost-playable space sim with realistically sized planets and star systems. However, as I built it, cool ideas occurred to me, and I introduced those ideas to the coding agent. Eventually, it tied the code into knots, and now that project is basically painted into a corner.
Why was AI able to handle one and not the other? The answer is simple! Or rather, simplicity/complexity. Somewhere between the extremes of simplicity and complexity, there exists a threshold that, once crossed, renders your AI model of choice less capable of producing clean results.
Step 1: Admit You Have A Problem
AA meetings and programs have long established that the first step towards recovery is recognizing and admitting that you have a problem. How do we know then that we’ve crossed that invisible complexity threshold? Perhaps in understanding where the problem comes from, we might be able to design a better tray so that when AI brings our orders to the table, it can carry more in one trip. For this, I look to my own experience building things with AI over the last year or two.
My starship bridge simulator game idea is not the first time I’ve encountered this issue. I’ve been running into this wall since day one of using AI to write code. I always seem to encounter it in the same way: after spending a day or two writing a spec that might span dozens of files, I tell the AI to “make it so!” My coding agent proceeds to build… something, based on the spec. All seems to be going well, until it seems to get distracted. It goes down one rabbit hole, then another, it seems to have one idea in mind, then does the exact opposite. It’s like it has ADHD and got bored, and no matter what I tell it, it can’t seem to stay on point and keep focused.
My theory is that complexity in a software spec comes mainly in 3 flavors:
- How relationships between requirements are managed in large specs.
- Collisions between similar or identical requirements, violating DRY.
- Contradictions between requirements, where later decisions go against earlier decisions.
As humans, we have a limited capacity to fit things into our “working memory”. AI has a similar problem: at some point, you’ve put enough things into context that they can no longer reason about them effectively. This has little to do with the size of the context window itself. Instead, it has to do with the size of the model and the complexity of the context you’re giving it. You can effectively drown a model in complexity in only a handful of paragraphs. This suggests that raw size matters least of all, because you can fit a lot into context in frontier models. It isn’t so much the amount of context you use, then, as it is the quality of that context.
Now at least, we can begin!
Managing Complexity
Let’s break out the 3 points above and analyze how we might address them.
Size Matters
As we already stated, a too-large spec isn’t so much the real root of our problem because even extremely large specs can still fit into the context windows of frontier models. However, there’s at least one way that we can effectively manage massive specs, and it mirrors how we would do this were we actually writing the software ourselves: abstraction.
The idea behind abstraction is to kind of wrap complexity up into a more general package which we can then more realistically reason about. That general package in object-oriented languages is usually referred to as a “class”, as in “classification”. Build a bunch of classes that encapsulate (see what I did there?) complex ideas, and you gain something of a “library of things” that can now interact.
We can do the same thing in our specs: spec things out at different layers of abstraction. Use multiple files, and use directories and organization just like you would in a software project. The result is a structured spec with layers that can be peeled away as needed so you don’t have to fit all of the complexity in your mind at once. You can, in theory, focus on just one element at a time, only delving into complexity when we need to. In other words, we set things up so that we can strategically ignore “unrelated” complexity.
Abstraction is useful because it gives us permission to ignore some stuff so that we have the ability to focus on other stuff. However, the fact that abstraction is useful does not mean that it is the truth. Just because we can ignore something doesn’t mean that the thing we are ignoring is unrelated to the thing we are focusing on. Most of the time, those things are very much related, but trying to keep that in mind exhausts our human “working memory” and we have no space left for the task at hand. Ultimately, this means we may incorrectly address the task at hand precisely because of the things we’re ignoring.
So, use abstraction in your specs, but be very careful about how you go about doing so!
When Stars Collide
Software engineers talk about the DRY (Don’t Repeat Yourself) principle often for a reason: code that respects this principle is often easier to fix, has cleaner architecture, and makes more sense. However, because they are written in English and are therefore difficult to validate and track, specs often violate DRY. When this happens in a spec, it can be a signal that the software you’ll produce from the spec needs a utility or helper module that implements that repeated requirement in a centralized location. Otherwise, you end up with multiple implementations of the same thing, and if any one instance of that requirement drifts, so too can the resultant code, such that things that were originally meant to be consistent can eventually contradict each other. The result? A tangled mess.
Nobody wants that!
Once again, we can take our signals from how we manage this issue in software: factor out those common parts into a central location. However, finding those common parts in a natural language spec is extremely difficult. Part of the problem is combinatorics: when you have a group of things that are all connected to each other, how many connections are there in total? That’s how many comparisons you’ll need to run, and this gets prohibitive, fast.
Luckily, we have new modern tooling we can take advantage of to address this problem. One of these is vector databases. A vector database with a good enough embedding model (a separate AI model whose job it is to read things and translate that into artifacts stored in a vector database) makes it possible for us to “index” a spec in such a way that you can search for natural language and concept matches. By creating that search space, we’re effectively reducing the number of comparisons we need to do because we really only need to compare requirements if they have similar subject matter. Similar subject matter results in similar vector database entries.
Or, you could always go “old-school” and just do all of the comparisons, one at a time. To be honest, this is a valid approach with AI: spin up several thousand sub-agents, give each one a high-level understanding of the project, and have each one run a single comparison. That’s a lot of comparisons though, and a lot of tokens to process all of them. Ultimately, you get diminishing returns, because in all honesty, some problems are cheaper to find and deal with when they are manifested as actual bugs in code instead of trying to go all-in on debugging a spec, but I still do believe that some effort on the spec side of things is warranted.
Fight!
Contradictions in the spec lead to contradictions in the code. When AI is the one writing your code, it now faces something of an existential crisis: how do we decide which of these contradicting ideas is correct? The same thing happens when an already-implemented requirement has changed in part or entirely, and you now need to do surgery; the AI has to decide between respecting your user prompt or respecting what is already in the code. Often, AI gets this wrong, because often, people do too.
So, what are we to do when requirements don’t agree?
This harks back to the same problem in collisions: how do we know when requirements collide? The solution here is the same as the solution there: an exhaustive search through all requirements to make sure none of them contradict each other.
However, this is another place where things can get more complicated: it is easy to figure out if two requirements contradict each other directly, but it is harder to figure out if they contradict each other indirectly through other requirements. It is possible and even common for a tertiary requirement to agree with both requirements you are considering, but when all three (or even more) are taken into consideration together, agreement falls apart somewhere. Simple pairwise comparison doesn’t find these “derivative” contradictions.
Luckily, this is one place where “going down the rabbit hole” can actually be a good thing. If you take the same approach as for collisions and “index” your entire spec into a vector database, you can then instruct AI to do the pertinent comparisons for contradictions, AND to do secondary searches on each related requirement to look for derivative contradictions.
…This is where I throw in a disclaimer: I haven’t explored this idea on actual projects enough to know how much it actually helps or whether it is worth pursuing, but I find it interesting to think about and potentially very important, so… yeah. Take this however you like.
All of this comparison work pays a dividend beyond a cleaner spec. Hunting down collisions and contradictions is also how you find your architecture: we discover those parts that can be abstracted away and referenced through links. We discover those parts that can be built into a whole layer of our system. We locate the hubs of logic, the essential signals that flow through common points and must therefore follow common formats and have common features.
Caveat
In all of this, we can go too far. Eventually, you have to actually write software, or you’ll never write software. At some point, the expense of pushing the spec further is greater than the expense of just implementing it and finding the bugs as you go.
I find it useful to time-box how long I work on the spec for a project based on how complex I believe the project to be.
The Answer
The answer to these problems is ultimately that there is no answer. They are part of why software engineering is a hard vocation, and why it will remain such even as AI “takes over” (i.e. turns us all into project managers). Still, just because you might not be able to completely solve the problem doesn’t mean you can’t do something about it!
So, here I am, at ElixirConf 2026 in downtown Chicago, listening to presentations about how AI is changing how people use Elixir, and the idea for Requiem finally crystallizes enough for me that I can start writing a spec.
I give my requirements: I need a command line tool for use by AI coding agents that:
- Records each requirement with a unique identifier, possibly a number or something.
- Records each requirement both in structured spec markdown files/folders and into a vector database.
- Provides tools to help AI look for and deal with collisions and contradictions.
- Provides tools to help AI identify potential architectural patterns in the spec so that the spec itself can be reworked to explicitly reflect those patterns.
I have my agent interview me on what I need, then I tell it to figure out the best architecture/language/framework for this monster.
That’s how I found myself in my hotel room at ElixirConf, building a command line utility in Go.
In Practice
When writing software for yourself, it is useful to dog-food what you write. You might be thinking, isn’t that what happens when you use your own software? I’m glad we agree!
So, I took Requiem for a spin, and immediately ran into a few bugs.
After a few rounds of that, I became convinced it was ready to go even further, so I began testing it on my starship bridge simulator game idea.
To do this, I started fresh (I’ve written about the need to throw things away sometimes).
A completely empty folder works nicely for this.
I ran git init then requiem init, then finally opened a Claude Code session and began telling it about my project idea.
It immediately got on-mission and started recording requirements. Okay, that’s… cool. It kept doing so as I worked through various systems. It then started guiding me to those areas of the requirements that seemed less fleshed out. Finally, it started finding collisions and contradictions, ALL OVER, in my brand-spanking-new spec. I thought it was a bug at first, and while the threshold for things was a little bit too aggressive, it was actually functioning just fine. Finally, at the end of that session, I had it give me feedback on how to make Requiem better, and it gave me a rather huge list of items, which I then fed into the Claude Code session operating on Requiem itself.
So far, it has helped me build a spec and stay unusually on-track myself. If ever I tell it something that contradicts or collides with something, it lets me know immediately, and then ensues a philosophical discussion on why I made the initial decision and why this new decision conflicts with it. Once that’s resolved, it goes back into the spec immediately. It then proposes ways to update the spec organization, or it just updates it, no questions asked, and often in ways that make perfect sense to me. Super cool!
Do I know if this project legitimately helps? No, I do not. I strongly suspect that it does though!
Spec Is The New Code
GitHub agrees with me on this philosophy: because of AI, the spec is becoming the new programming language. The AI is the compiler, and it’s a super fancy one because you can talk to it and ask it things. To help this paradigm along, GitHub built Spec Kit, a tool that is meant to help in the process of turning a software spec into an implementation. So, why use Requiem when you could use Spec Kit?
Simple: they don’t solve the same problem. Spec Kit focuses on turning specs into implementations. Requiem focuses on increasing the quality of your specs, so that your software can stay simple and your coding agent can stay focused. In other words, build your spec with Requiem, then implement it with Spec Kit.
These are accidentally, beautifully complementary tools! They also stand as a kind of evidence of the underlying philosophy about LLMs turning specs into the new programming language. No longer should we work on the level of code, but rather, the level of the spec. Build a good enough spec, and your AI agent should have enough information from that to turn it into an actually working project.
Conclusion
So, if you feel the need, take Requiem (and maybe Spec Kit also) for a spin! Let me know in GitHub issues and such what you think of the project and give me a star if you found it helpful.
Or… you could try building your own with AI, and learn a thing or two along your way, which I also highly recommend.
Because, when the spec is your programming language, code becomes cheap!