Claude Code: The Unreasonable Effectiveness of Simplicity
🇧🇷 Leia em Português
A team here at work had a very specific problem: their voice agent didn’t know when to end a conversation.
The product is neat. Someone opens a link, an AI asks a sequence of questions by voice, and at the end it all turns into structured data. A conversational survey. The kind of thing you send to a client and they answer by talking, no form involved.
The missing piece was the ending. The agent asked the questions, got the answers, and just stayed there. Floating. A human feels when it’s time to wrap up a chat. The machine doesn’t.
Lately I’ve been taking this kind of problem and building a PoC to leave as an example - a running case my colleagues can open, understand, and adapt to their own context. That’s what I did here.
I could have done what’s fashionable now: sit down and write a giant document before touching any code. A spec. Requirements, state diagram, the fifteen academic ways to detect end of conversation, acceptance criteria for each one. Spec-driven development, which is being sold as the grown-up way to work with AI.
I didn’t. And that’s what this post is about: why the simplest possible thing solved it, and why the big document would have slowed me down.
The only document worth writing came before everything - and it wasn’t a spec
The first thing I did was ask for research. One prompt, written in a hurry, typos and all:
claude, my teammates are working on a project that is an AI agent, a voice AI agent. The agent goes through a sequence of questions and has some interactions in the middle. The agent, however, cannot understand when to finish a conversation by itself. Websearch algorithms, models and all techniques that can be used for it. Fan out agents, look into HuggingFace, GitHub, Kaggle, wherever you find useful to look into.
Look at what this document is. It doesn’t describe what I was going to build. It maps what the world already knows about the problem: how LiveKit, Pipecat, and Vapi end a call, which endpointing models exist, what others have already tried and where they broke.
That’s the distinction almost nobody draws properly.
Research reduces an unknown you cannot guess from your chair. It’s external knowledge, something that already exists outside your head, waiting for you to go get it.
A spec tries to guess an unknown that only the running system can answer. It’s an internal guess dressed up as certainty.
One you collect. The other you invent. And guess which of the two everyone spends their first day producing.
I saved the result:
save all this research and reference to a file, in a new folder.
And only after having the map in hand did I ask for the prototype:
plan a PoC on this. A voice agent that runs on browser, with a human voice. It should go through a list of AI-crafted questions. The backend team is in Go, so it’s preferable to keep it that way.
The first real line of code was born 37 minutes after the first prompt. No PRD. No alignment meeting about the schema. Research, a half-page plan, and hands on the keyboard.
That prompt was not a Google search
When I ask for research like that, Claude Code doesn’t open Google and paste the first result. That prompt of mine up there became a /deep-research, a command that ships with the tool. I described the problem in sloppy English; Claude picked the tool and ran it.
Under the hood, /deep-research is a workflow: a script that orchestrates several subagents, each on a slice of the task, storing results outside the main context window so it doesn’t clog up. It runs five phases - the logic any good human researcher would follow, except in parallel and without laziness:
- Scope - takes my question and breaks it into about 5 different angles. One goes after the state of the art, another the academic papers, another the skeptical/contrarian view, another the practical implementation. Angles that don’t overlap.
- Search - fires 5 search agents in parallel, one per angle. Five simultaneous searches, not one.
- Fetch - merges everything, removes duplicate URLs, and actually fetches the ~15 best sources. Not the SERP snippet: the whole page. From each one, it extracts falsifiable claims, with the direct quote backing each one.
- Verify - and here’s the trick. Every claim goes through 3 skeptical reviewers, each with an explicit order: try to refute this. If 2 out of 3 knock it down, the claim dies. Marketing, cherry-picked benchmarks, forums, old papers from a fast-moving field: all of it gets filtered before it reaches me.
- Synthesize - merges the semantic duplicates, ranks by confidence, and spits out a report with cited sources.
Now compare that with what you do on Google.
You type a query, get ten blue links, open eight tabs, read half of three, forget two, and end up saving a bookmark you’ll never revisit. One query. One angle. Zero verification. You are the loop - the tired one, the one who skips the seventh tab because enough is enough.
In Claude Code the loop is the machine, which doesn’t get tired at the seventh tab or the fiftieth. And there’s a difference deeper than speed: on Google you search for pages; in the workflow you search for verified claims. One hands you a pile of tabs. The other hands you a report where every sentence has already been beaten up by three skeptics.
The result of that afternoon is open in the project’s RESEARCH.md. It has 45 distinct sources - arXiv, GitHub, HuggingFace, LiveKit and Vapi docs - organized by theme, with the open source turn-taking models separated from the academic datasets separated from the vendor tools. That came out of one prompt.
Sit down and try to assemble that same bibliography by hand, on Google, in one afternoon. Good luck. You’ll get to the tenth tab and give up - and the 35 missing sources were exactly the ones that would have saved you from reinventing endpointing from scratch.
The real design was written by the bugs
Here’s the part no spec would have caught, because it’s impossible for it to catch.
The real product was born from me talking to the thing and complaining about what was bad. Prompt by prompt, each one fired right after I spoke into the microphone and heard the agent get it wrong:
I’m still speaking and she asks if I’m still there, but then she could get my answer…
VAD timeout too short. She would cut me off mid-sentence to ask if I was still there. That wasn’t in the spec because it couldn’t be. You only find that out with a microphone in your hand and half a sentence in your mouth.
she only says “that’s everything I wanted to ask” and it cuts. Why is this happening?
The goodbye cut off in the middle of its own goodbye.
it repeated an already-answered question.
She was re-asking something I had already answered, because a classifier mislabeled my answer.
And the most subjective one of all, the one that really ties the knot:
this intro is not like a human would talk.
How do you write the rule “the opening has to sound human” in a document? You don’t. That was the whole point of the product - fluid, not robotic - and that is unspecifiable in prose. You don’t feel a PRD. You feel a conversation.
Each of these defects was discovered floating in front of the prototype, not predicted in a document. And it couldn’t have gone any other way. A voice agent is made of what happens in the gap between speech and response, and that gap doesn’t fit in a bullet point.
The eval became the spec - only growing backwards
Every time I found a new behavior, the request was the same:
add this to the eval.
That’s how the “spec” was born. It didn’t predict the behaviors. It accumulated them as they showed up. The document grew backwards, from the reality that ran, not from my Friday-afternoon imagination.
Notice the inversion. In spec-driven, the document comes first and reality tries to catch up. In what I did, reality came first and the document ran after it to record what had already proven itself true.
The same went for writing things down:
keep it documented, every single step must be documented and revalidated on every single change.
Docs after validation, describing what a run had proven. Not before, describing what I hoped would work. The project’s CLAUDE.md only showed up at the very end, when there was something to describe.
What was left at the end
A PoC good enough to solve the team’s internal problem.
It runs in the browser. It generates the questions from a preset, the agent runs the survey with a human voice, reacts to what the person says, and - the thing that mattered from the start - knows when to end. Including when the person disappears halfway through and she has to detect the silence and finish on her own, with dignity.
No hundred-page document. A tight loop, repeated to exhaustion: build, test live, listen to what breaks, fix it, pin it in the eval. Again. And again.
It was fast not despite being simple. It was fast because it was simple.
Why simplicity was too effective
There’s a famous Wigner essay about the unreasonable effectiveness of mathematics in the natural sciences. The idea is that a simple tool sometimes explains far more than it had any right to explain. That was exactly the feeling here.
And the dumb trial-and-error loop that handled everything has a name: it’s just a chat with Claude Code open next to the browser. Short prompt, eyes on the prototype, short prompt again. No ceremony. A problem the big-document crowd would treat as a two-week project became an afternoon of conversation.
If you got this far looking for the secret trick, I owe you a disappointment: there isn’t one. No clever architecture, no magic prompt, no secret technique. I described the problem in sloppy English, let the tool do the research, built the dumbest thing that worked, and kept fixing whatever broke in front of me. I didn’t do anything fancy - and that’s the entire post. Simplicity wasn’t a detail of the path. It was the path.
And the reason is kind of obvious once you stop to look.
Spec-driven tries to turn the hardest unknowns - the ones that only exist in the behavior of the running system - into confident prose, before the system exists. It spends the most expensive bullet early, at the exact moment you know the least about the problem.
I only front-loaded the one unknown that could be front-loaded: the domain research, the knowledge that already existed out there. The rest of the unknowns - the design ones - I let resolve themselves, empirically, in the friction with the microphone.
It’s not that documents are useless. It’s that a document is good for recording what you discovered, and terrible for pretending you already know. Its time is after the first contact with reality, not before.
And yes, I’m aware this is a PoC, not a product running in production with an SLA and a pager going off at three in the morning. But the order doesn’t change with the size of the thing. Product, task, one-afternoon prototype: you start with research, understand what you want to do, and then you do it. That was literally the arc of this post. Spec-driven doesn’t invert that order - it just pushes the understanding part to before its time, while it’s still a guess.
The spec-driven crowd will spend day one writing an exquisite section about the barge-in timeout - which a real microphone corrects in ninety seconds.
You probably don’t need that. Research what can be researched, build the dumbest thing that works, and let reality write the rest of the spec for you. It writes better, and it never delays the delivery.
Thanks for reading!