AI · Software Engineering · Vibe Coding, Deliberately, part two · Oct 2026 · 8 min read

Spontaneous vs Deliberate: Two Ways I Vibe Coded

Apoorva JoshiAI Solution Consultant (Presales) · Product Manager

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.

BeatKeeperMealPartner
BuiltJanuary 2026June 2026
What it doesA team of AI agents that runs a weekly content campaign for busy professionalsA WhatsApp assistant that suggests meals to university students
AI patternMulti-agent orchestration on a hosted modelRetrieval-augmented generation with a small local model
StackPython, Claude API, FastAPI, Streamlit, Playwright, pytest, mypyNode.js, Ollama, SQLite vector search
Core structureFive layers: Sense, Decide, Produce, Assure, Package, called only by one orchestratorConversation state machine, then filter and hybrid ranking, then confidence tiers, then a templated reply
Where the LLM is trustedDrafting, repurposing, fact and compliance checking, always behind citations and human approvalOnly narrow, schema-constrained tasks; it never writes recipe content or links
ProcessSpontaneous: idea first, structure emergedDeliberate: 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.

Idea→Scaffold→Grow by prompt→Coupling wall→Refactor→Retrofit tests

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.

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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 v2.3→LLM boundary→Failure modes→Scope→Tests first→Build slice→Review diff
  1. 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).
  2. 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.
  3. 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.
  4. 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.
  5. 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.

StageBeatKeeper (spontaneous)MealPartner (deliberate)
RequirementsLived in the prompt history; reconstructed laterA versioned document, iterated to v2.3 before any code
ArchitectureEmerged from accumulated prompts, then refactored into five layers and one orchestratorDesigned up front: state machine, staged matching, ranking, confidence tiers
Interfaces between partsLoose dictionaries first, typed contracts (mypy) added during refactorDefined in the requirements; model and messaging layers abstracted from day one
LLM boundaryDecided role by role as each agent was addedOne rule for the whole system: code decides, model assists, schema-constrained output only
Non-functional rulesDiscovered, then enforced by tests after the factStated as requirements (scope, scale, hardware, security) before the build
TestingRetrofitted: pytest for hard rules, golden-dataset evals for qualityTest-first per slice, derived from acceptance criteria
Unit of changeWhole features in one agent session, large diffsOne slice per session, reviewed diff, commit on green
Failure handlingFixed as breakages surfaced downstreamDesigned: confidence tiers, honest no-match reply, unmet-request queue
Context for the agentThe conversation itself, which drifted as it grewThe requirements document, re-read fresh each session
Time to first working demoAbout a daySeveral 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)

Pros
  • 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.
Cons
  • 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)

Pros
  • 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.
Cons
  • 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.

SituationLean towards
The problem itself is unclear and you need to see it to understand itSpontaneous
A throwaway spike or a demo for a conversation next weekSpontaneous
The LLM touches facts, money, health, personal data or anything a user will act onDeliberate
Many components that must hand data to each other reliablyDeliberate
You expect to scale, swap models or hand the code to someone elseDeliberate

Next time I will run both, in sequence, on purpose:

  1. Spike spontaneously for a day or two. Vibe a throwaway prototype to learn what the problem really is. Treat the code as disposable.
  2. 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.
  3. 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.