EST. 2021  •  OSLO, NORWAY

Skui.io

Homelab & Self-Hosting

← Back
blog

The Prompt Is Not the Product

Yesterday I wrote that AI amplifies whatever understanding you bring. Here's the practice that makes that concrete: write the spec before the AI writes the code.

The Prompt Is Not the Product

The part I left out
#

Yesterday I wrote that AI will happily build you a house of cards — that the tool amplifies whatever understanding you bring to it. A few people will fairly ask: fine, but what does bringing understanding look like on a Tuesday? What do you actually do differently?

Here’s the honest answer: I almost never let the AI write code from a prompt. I let it write code from a spec.

The difference sounds academic. It’s the whole game.

Vibe coding has a half-life
#

The default way people work with AI looks like this: type a wish into the chat, get plausible code, run it, notice it’s not quite right, type another wish, get another patch. Repeat until it works — or until you can no longer tell whether it works.

Every iteration drifts a little. The model doesn’t know what you meant, only what you said, and what you said is scattered across forty messages of “no, not like that”. By the end, the only complete description of your feature is the conversation itself — and the conversation is gone tomorrow. Next session, next model, next re-prompt: you’re rebuilding intent from memory and vibes.

That’s the house of cards from yesterday’s post, one story taller.

Spec first
#

Here’s where I have to be honest about the word “spec”, because mine rarely looks like a document — and never like a Jira epic.

By the time the AI writes its first line, the decisions that matter are already made, and I made them:

  • The stack is chosen. Python, standard library, SQLite. Not because it’s fashionable — because I can hold all of it in my head. The AI doesn’t pick the architecture; it works inside one.
  • The constraints are hard. The database is isolated — not reachable from the internet, period. No inbound ports. Secrets stay out of the repo.
  • Zero dependencies. The Ethica suite runs on the standard library alone, because every dependency is code I didn’t write — and I want control. An AI draft that opens with pip install is dead on arrival.
  • Language and syntax are dictated. I decide the language, the idioms, the patterns. The AI types faster than I ever will; it doesn’t get to have taste on my behalf.

And “done” isn’t a feeling either — it’s executable:

  • Smoke tests. Every app has a suite that builds the real containers and hits the real endpoints. Green means done. “Looked right in the chat” means nothing.
  • CVE scans on my own images. The same scanning I’d point at anyone else’s software, pointed at mine.

Write all of that down for one feature, and you get something like this — the status widget from the previous post:

# spec: homelab status widget

Goal: the front page shows live homelab health
without exposing anything.

Must:
- collector reads Docker state via a read-only socket-proxy
- collector pushes snapshots to the site's API,
  authenticated with a token
- the widget renders the last snapshot it received

Must not:
- no inbound ports opened on the lab
- no Docker socket mounted in the web container
- no third-party monitoring service

Done when:
- front page shows container states + host uptime
- killing the collector makes the widget go stale
  within two minutes — it must never show wrong data

That widget exists, and every line above was enforced — but the file never was. The spec lived as decisions made before the first prompt and tests run after the last one. Whether you write it down or carry it in, the function is identical: when the code comes back, I don’t review it against my mood. Did it mount the socket? Violation, reject. Did it pull in a framework? Violation, reject. The spec turns “hmm, this feels off” into “that breaks a rule I set on purpose”.

That’s the difference between me and the vibe coder. Not a prettier file — a set of decisions the AI never gets to make, and a definition of done that doesn’t care how convincing the chat looked.

Why this works on a machine
#

Large language models have a very particular skill profile: they are astonishingly good at filling in — syntax, boilerplate, idiom, the eighty percent of code that is just plumbing — and genuinely bad at guessing what you meant. A prompt asks the model to do both at once. A spec splits the job: intent stays human, plumbing goes to the machine.

The spec also does something the chat never will: it survives. The constraints live in the architecture itself — the isolated database, the empty requirements file, the stack I can hold in my head. The definition of done lives in the smoke suite, in the repo, next to the code. Six months from now, when I rebuild a service in a different language or hand it to a different AI, those are still there, still true, still the judge. The forty-message conversation that produced the first version? Evaporated, and nothing of value was lost — because the conversation was never the artifact.

Most of the Ethica suite was built exactly this way — it’s why the project card on this site’s front page carries an sdd tag: spec-driven development. Feature by feature: spec, implement, review against spec, commit both.

This is not waterfall
#

Before anyone prints out the word “process” and hits me with it: none of this is paperwork. When implementation teaches me something — and it always does — the constraints and the tests change with the code, in the same commit. It’s not a contract negotiated upfront and defended forever. It’s a running answer to the question “what is this supposed to do?”, kept somewhere more durable than my head — and more durable than the chat.

A few decisions up front, and in return: reviews become mechanical, drift becomes visible, sessions become disposable, and the AI gets exactly what it’s good at and nothing it’s bad at.

The product
#

Here’s the shift I think is actually underway, beneath all the noise: code is becoming cheap. Regenerable. Almost disposable. If the spec is good, I can throw the implementation away and have a new one by lunch — I’ve done it.

What’s scarce is the thing the machine cannot produce: knowing what you want, precisely enough to recognize when you haven’t gotten it.

The prompt is not the product. The spec is.


If you’re building with AI: what survives your chat sessions? If the answer is “nothing but the code” — then before the next feature, decide three things the AI doesn’t get to decide, and one test that defines done. See what it changes. Then tell me about it. You know where to find me.