Spontaneous vs Deliberate: Two Ways I Vibe Coded
Same builder, same AI coding tools, two very different processes: BeatKeeper was built by following the idea wherever it led, MealPartner by locking the requirements first and then building test by test.
I built BeatKeeper in January 2026 and MealPartner in June 2026. Both were vibe coded, in the loose sense: I described what I wanted to an AI coding agent and steered by results rather than by typing the code myself. What changed between them was not the tooling. It was where the thinking happened: in the conversation with the agent, or before it started.
This post compares the two processes in technical detail, and is honest about what each one bought me and what each one cost. In my earlier post, Agile, Waterfall and the Vibe, I argued for a hybrid in principle. This is the evidence from my own builds.
The two projects at a glance
The projects are different enough in shape that the process difference is not just style; each process suited, and strained against, a different kind of system.
| BeatKeeper | MealPartner | |
|---|---|---|
| Built | January 2026 | June 2026 |
| What it does | A team of AI agents that runs a weekly content campaign for busy professionals | A WhatsApp assistant that suggests meals to university students |
| AI pattern | Multi-agent orchestration on a hosted model | Retrieval-augmented generation with a small local model |
| Stack | Python, Claude API, FastAPI, Streamlit, Playwright, pytest, mypy | Node.js, Ollama, SQLite vector search |
| Core structure | Five layers: Sense, Decide, Produce, Assure, Package, called only by one orchestrator | Conversation state machine, then filter and hybrid ranking, then confidence tiers, then a templated reply |
| Where the LLM is trusted | Drafting, repurposing, fact and compliance checking, always behind citations and human approval | Only narrow, schema-constrained tasks; it never writes recipe content or links |
| Process | Spontaneous: idea first, structure emerged | Deliberate: requirements to v2.3, then build with tests |
BeatKeeper: the spontaneous build
BeatKeeper started as an idea and a first prompt, not a document; its architecture was discovered in the build and then hardened after the fact.
The idea was simple to say: agents that watch a professional's domain, an editor that picks topics, a writer that drafts, and a checker that catches mistakes. I opened VS Code with an AI coding assistant and asked for exactly that. Here is how it unfolded, technically.
- Scaffold from a sentence. The first session produced a working pipeline: a Python script that called the Claude API once per role, passing plain strings between them. It ran end to end within a day, which was exhilarating and is the main argument for this way of working.
- Grow one role at a time. Each new idea became a prompt to the agent: add a regulatory watcher, add a repurposer for short formats, add a visual designer. Each role arrived with its own prompt text inline and its own way of calling the model. The system grew sideways, not by design.
- Hit the coupling wall. Once there were around eight roles, roles had started calling each other directly and passing loosely shaped dictionaries. A change to the writer's output broke the fact checker downstream, silently. The agent fixed each break locally, which often created the next one.
- Refactor into the structure it needed. I stepped back and asked for the architecture the system had been hinting at: five layers (Sense, Decide, Produce, Assure, Package) with one orchestrator as the only component allowed to call a role. Typed data contracts checked by mypy replaced the dictionaries. All model calls were routed through one logged client, and prompts were pulled out into versioned files per role.
- Retrofit quality and rules. Output quality was judged by reading drafts until I built a golden dataset of reference cases and ran evals against it. The non-negotiables, such as citation-enforced claims, mandatory human approval of the topic and of the final pack, no auto-posting, no scraping of login-walled sites, and respecting robots.txt, became pytest tests only after the behaviour already existed.
The finished prototype is disciplined. The path to it was not: a large share of the effort went into restructuring code that had been generated quickly in the first place.
MealPartner: the deliberate build
MealPartner did not get a line of code until its requirements reached version 2.3 with every open question closed; then a coding agent built it in tested slices against that document.
- Requirements as the product. Several sessions produced only a requirements document. It defined the user (a university student), the conversation flow (pick a food type first: main meal, dessert, cocktail or mocktail; protein and carb questions only for main meals), and the reply rule (show the best-ranked match, then offer more options).
- Decide what the model may and may not do. The key design decision was a boundary: the code decides, the model assists. Free text is matched in stages, exact then fuzzy then a constrained LLM call, and the LLM only ever returns schema-constrained output. It never writes recipe content or links. That single rule removed most hallucination and prompt-injection risk before any code existed.
- Write the failure modes down. Confidence tiers decide whether a match is shown, shown with a caveat, or not shown. When nothing fits, the bot says so and logs the request to a ranked queue for the recipe curator; silence counts as failure. These were requirements, so they became test cases rather than afterthoughts.
- Fix scope and constraints explicitly. One test user, designed for 100+. English only. Allergies and dietary restrictions out of scope. No encryption at rest. Target machine: Windows 11, AMD Ryzen AI, 32 GB RAM, which is why a small local model on Ollama and SQLite vector search, not a hosted stack. The model and messaging layers sit behind interfaces so a hosted LLM or the official WhatsApp API can be swapped in later.
- Hand off, then build test-first. The finalised document went to Claude Code as the source of truth. Each slice (intent detection, the state machine, filtering and hybrid ranking, confidence tiers, templated replies) started from tests derived from the acceptance criteria, then implementation to make them pass, then my review of the diff.
The first code arrived much later than BeatKeeper's did. Very little of it has had to be rewritten.
Process compared, stage by stage
The difference is not whether each stage happened, but when it happened and what forced it.
| Stage | BeatKeeper (spontaneous) | MealPartner (deliberate) |
|---|---|---|
| Requirements | Lived in the prompt history; reconstructed later | A versioned document, iterated to v2.3 before any code |
| Architecture | Emerged from accumulated prompts, then refactored into five layers and one orchestrator | Designed up front: state machine, staged matching, ranking, confidence tiers |
| Interfaces between parts | Loose dictionaries first, typed contracts (mypy) added during refactor | Defined in the requirements; model and messaging layers abstracted from day one |
| LLM boundary | Decided role by role as each agent was added | One rule for the whole system: code decides, model assists, schema-constrained output only |
| Non-functional rules | Discovered, then enforced by tests after the fact | Stated as requirements (scope, scale, hardware, security) before the build |
| Testing | Retrofitted: pytest for hard rules, golden-dataset evals for quality | Test-first per slice, derived from acceptance criteria |
| Unit of change | Whole features in one agent session, large diffs | One slice per session, reviewed diff, commit on green |
| Failure handling | Fixed as breakages surfaced downstream | Designed: confidence tiers, honest no-match reply, unmet-request queue |
| Context for the agent | The conversation itself, which drifted as it grew | The requirements document, re-read fresh each session |
| Time to first working demo | About a day | Several requirement sessions before any code |
Pros and cons
Spontaneous building buys speed and discovery at the price of rework; deliberate building buys correctness and safety at the price of a slow start.
Spontaneous (BeatKeeper)
- Fastest path to something real. A working end-to-end pipeline in about a day. Nothing clarifies an idea like watching it run.
- Discovery. Several of BeatKeeper's best features, such as the repurposer and the Assure layer, came from seeing early output and reacting to it. I would not have specified them on day one.
- Low cost of being wrong early. When the problem itself is fuzzy, a spec written too soon fixes the wrong thing in place.
- Momentum and motivation. Visible progress every session kept a solo side project alive.
- Architecture by accident. Structure followed the order of my ideas, not the shape of the problem, and cost a major refactor to undo.
- Silent coupling. Untyped hand-offs between roles let one change break another without any error.
- Inconsistent LLM boundaries. Each role trusted the model to a different degree until I imposed one logged client and citation enforcement.
- Tests as archaeology. Writing tests after the fact meant first working out what the code actually did, then deciding whether that was what I wanted.
- Context drift. Long agent sessions accumulated confusion; the conversation was the only record of intent, and it contradicted itself.
Deliberate (MealPartner)
- Risk designed out, not patched out. The decision that the model never writes recipe content removed whole classes of hallucination and prompt-injection risk before any code existed.
- A stable source of truth for the agent. Every Claude Code session started from the same document, so there was little drift between sessions.
- Tests are cheap and meaningful. Acceptance criteria translated directly into test cases, including the unhappy paths.
- Smaller, reviewable diffs. One slice per session made it realistic to read every change.
- Scale and swap built in. Abstracted model and messaging layers mean moving from one user to 100+, or to a hosted model, is a contained change, not a rewrite.
- Slow start. Several sessions of thinking with nothing to click; harder to stay motivated and harder to show anyone.
- Assumptions untested by reality. A requirements document can be internally consistent and still wrong about how students actually talk to a bot. Only real use will tell.
- Risk of over-specification. Some decisions locked early might have been better made after seeing the system run.
- Process overhead. For a one-user prototype, versioned requirements and test-first slices can feel like ceremony.
When to use which, and what I would do next time
Neither process wins outright; the right one depends on how well you understand the problem and how much damage a wrong answer can do.
| Situation | Lean towards |
|---|---|
| The problem itself is unclear and you need to see it to understand it | Spontaneous |
| A throwaway spike or a demo for a conversation next week | Spontaneous |
| The LLM touches facts, money, health, personal data or anything a user will act on | Deliberate |
| Many components that must hand data to each other reliably | Deliberate |
| You expect to scale, swap models or hand the code to someone else | Deliberate |
Next time I will run both, in sequence, on purpose:
- Spike spontaneously for a day or two. Vibe a throwaway prototype to learn what the problem really is. Treat the code as disposable.
- Write the requirements from what the spike taught. Including the LLM boundary, the failure modes and the out-of-scope list, which are exactly what BeatKeeper discovered painfully.
- Rebuild deliberately. Fresh repository, the document as the agent's source of truth, test-first slices, every diff read.
Since writing this, I came across Lightsprint, a Y Combinator-backed platform that packages much of this hybrid for teams rather than solo builders. A PM, designer or engineer describes a change in plain language, the team agrees on a visual plan grounded in the existing codebase, cloud agents build it in parallel, and everyone clicks through a live preview before the change arrives as a pull request for engineers to review and merge. In the terms of this post, it keeps the speed of spontaneous building while putting the deliberate parts, agreement up front and review at the end, where they belong. If you would rather try this way of working than assemble the process yourself, it is worth a look at lightsprint.ai.
Spontaneity is good for finding the shape of an idea. Deliberation is good for making that shape safe to trust.
BeatKeeper taught me the first; MealPartner taught me the second. The mistake is using either one for the other's job.
Let's talk
I'm open to roles in AI solution consulting (presales) and product management.