There is a category error running loose in our industry right now, and it is costing teams real money.
The error is treating vibe coding and agentic engineering as the same activity performed at different levels of enthusiasm. They are not the same activity. They have different units of work, different failure modes, different artifacts, and, this is the part that matters, different economics when you point them at a production payment system that moves other people's money.
I build distributed systems for a living. Lately most of that has been payment infrastructure, which is the least forgiving place I know of to be wrong. Over the last while I have converged on a workflow that is roughly two-thirds spec-driven development and one-third loop engineering, implemented as three Claude Code skills that hand off to each other.
This post is that workflow, plus the things it broke that nobody warned me about.
The distinction, stated sharply
| Vibe coding | Agentic engineering | |
|---|---|---|
| Optimizes for | Time-to-first-output | Time-to-verified-increment |
| Unit of work | A file, a function, "the thing I asked for" | An independently testable vertical slice |
| Where intent lives | In a prompt that scrolls away | In a durable, reviewed artifact |
| Verification | Visual, manual, by you | Automated, delegated, adversarial |
| Context | Hoped for | Engineered from a source of truth |
| Characteristic failure | Shipping something you can't explain | Caught by the loop, not by you at 2am |
Vibe coding is a legitimate mode. I use it constantly for spikes, throwaway scripts, and exploring an unfamiliar API. It is a terrible mode for anything with an idempotency requirement.
The rest of this post is about the second column.
Spec-driven development plus loop engineering
Two ideas are doing the heavy lifting here, so let me define them the way I actually use them.
Spec-driven development means intent lives in an artifact that outlives any single agent session. Not in a prompt. Not in your head. Not in a Slack thread. In a document an agent can re-read at the top of every run, that a human reviewed and approved, and that changes through a visible edit rather than a vibe shift.
Loop engineering means deliberately designing the feedback loop the agent operates inside. What can it execute? What tells it that it is wrong, quickly and unambiguously? Where must it stop and escalate to a human?
A model without a tight loop is a very expensive autocomplete. A model with a tight loop is a colleague who never gets bored of running the test suite.
Neither is a prompting trick. Both are systems design applied one level up from the code.
The workflow: three skills, one handoff chain
rough prompt
│
▼
┌──────────────┐ clarifying questions ──▶ human
│ /to-prd │◀────────────────────────── ┘
└──────┬───────┘
│ PRD: user stories · outcomes · invariants · OUT OF SCOPE
▼ (posted to GitHub Issues, reviewed before any code exists)
┌──────────────┐
│ /to-issues │
└──────┬───────┘
│ vertical slices, each independently testable, each linking the PRD
▼
┌──────────────┐ ┌─────────────────┐
│ engineering │───────▶│ parallel slices │
│ skill │ │ 1 2 3 4 │
└──────┬───────┘ └────────┬────────┘
│ │
│ ┌──────────────────┘
│ ▼
│ validation fails ──▶ assumption was wrong
│ │
│ └──▶ PRD updated ──▶ UNDONE slices re-derived ← the outer loop
▼
human gate (blast radius, not difficulty)
Enter fullscreen mode Exit fullscreen mode
1. /to-prd, turn a vague intent into a specification
Every feature and every non-trivial bug starts here. I give it a rough prompt. Sometimes embarrassingly rough, a sentence and a link to a Sentry issue.
The skill then does four things:
- Extrapolates the prompt into candidate requirements
- Reads the actual codebase to ground those requirements in what exists
- Asks me clarifying questions
- Emits a full PRD, user stories, outcomes, explicit out-of-scope, and posts it to GitHub Issues
Step three is the entire value proposition, and it took me a while to see that.
The clarifying questions convert ambiguity into a decision at minute three instead of a defect at week six. When I write "add retry logic to the settlement webhook," a vibe-coded session gives me retry logic. /to-prd comes back and asks:
- Idempotent on the provider's key, or ours?
- What happens to ordering when attempt two lands before attempt one?
- Does a poisoned message go to a DLQ, or block the partition?
- Is partial settlement a valid terminal state?
I did not know the answer to two of those. That is the point. The gap between "what I asked for" and "what I meant" is where production incidents are manufactured, and a specification pass is a machine for finding that gap early.
The out-of-scope section is load-bearing
I want to be emphatic about this one. Agents are enthusiastic. Given a settlement webhook to fix, an unconstrained agent will refactor your retry utility, introduce a circuit breaker, and rename three things on the way past.
An explicit out-of-scope list is the cheapest guardrail in the entire workflow. It is the difference between a 200-line diff and a 2,000-line diff that nobody can review.
Posting to GitHub Issues rather than a local file is deliberate: it puts the spec where the team already lives, gives it a URL, makes it commentable, and makes review of the specification a first-class event, before a single line of implementation exists.
2. /to-issues, decompose into vertical slices
This skill takes the approved PRD and breaks it into implementation issues. The decomposition rule is the important part: vertical, never horizontal.
HORIZONTAL (org-chart slicing) VERTICAL (what I actually use)
┌───────────────────────────┐ ┌────┐┌────┐┌────┐┌────┐
│ frontend ticket │ │ UI ││ UI ││ UI ││ UI │
├───────────────────────────┤ ├────┤├────┤├────┤├────┤
│ backend ticket │ │ API││ API││ API││ API│
├───────────────────────────┤ ├────┤├────┤├────┤├────┤
│ migration ticket │ │ DB ││ DB ││ DB ││ DB │
└───────────────────────────┘ └────┘└────┘└────┘└────┘
nothing testable until all each slice testable
three land simultaneously on its own, on merge
Enter fullscreen mode Exit fullscreen mode
Horizontal slicing is an artifact of the org chart, not of engineering. Its defining property is that nothing is testable until everything lands. You accumulate work in progress for two weeks and discover on integration day that the API contract you both agreed to meant two different things.
Vertical slicing means every slice cuts through the whole stack: thin, but complete. Each push can be validated on its own. The next push iterates on top of it.
This matters more with agents than it ever did with humans, for a specific reason:
The validation signal is the cheapest resource you have, and slicing determines how often you get one. If a slice is independently testable, the agent self-corrects and the loop closes without you. If it isn't, you are the test suite, and you've just reintroduced yourself as the bottleneck in the exact place you were trying to remove yourself from.
The second benefit is the one I underrated at the start. When validating slice two reveals that a PRD assumption was wrong, that corrected context cascades downstream to slices three through seven before an agent ever touches them. You are not fixing seven implementations. You are fixing one document and re-deriving the remaining work from it.
A wrong assumption caught in slice two costs an edit. The same assumption caught at integration costs a rewrite.
That cascade is the loop engineering part. The inner loop is agent writes code, tests fail, agent fixes code. The outer loop is validation invalidates a spec assumption, spec updates, undone work re-derives.
3. The engineering skill, execute, in parallel, with gates
The third skill picks up the generated issues, each referencing the parent PRD, and executes them.
Parallelism where the dependency graph allows it. Independent slices run concurrently. This is where the wall-clock gains actually come from, not from the model typing faster than you, but from four slices progressing at once while you're in a meeting.
Human-in-the-loop markers. Some issues are flagged as requiring a human. The skill prepares, stops, presents, and waits. Everything else runs AFK.
My gating rule is blast radius, not difficulty:
| Runs unsupervised | Requires me in the room |
|---|---|
| Gnarly reconciliation algorithm | Schema migration |
| Complex parsing / transformation | Any money-moving code path |
| Test scaffolding, fixtures | Authorization boundary changes |
| Refactors covered by property tests | Anything whose failure is worse than a revert |
An agent can write a hard reconciliation algorithm unsupervised, because a wrong one fails a property test. Difficulty is a bad proxy. Cost-of-being-wrong is the right one.
Why payments make all of this non-optional
You can run a sloppy version of this on a CRUD app and mostly get away with it. Distributed systems that move money will not extend you that courtesy.
Here is the failure mode that made me take specification seriously:
// Looks fine. Passes review. Passes its tests.
async function settle(payment: Payment) {
return retry(3, () =>
provider.charge({
amount: payment.amount,
currency: payment.currency,
})
);
}
Enter fullscreen mode Exit fullscreen mode
That code double-charges a customer under a network partition, because the retry has no idempotency key and the underlying delivery guarantee was at-least-once all along. A timeout is ambiguous, the charge may well have committed.
// The invariant, made explicit in code because it was explicit in the spec.
async function settle(payment: Payment) {
const key = idempotencyKey(payment.id, payment.attemptEpoch);
return retry(3, () =>
provider.charge({
amount: payment.amount,
currency: payment.currency,
idempotencyKey: key, // stable across retries, this is the whole ballgame
})
);
}
Enter fullscreen mode Exit fullscreen mode
Nothing about the first version looks wrong on review. It is wrong at the level of invariants, and invariants are invisible in a diff.
Which means the specification has to carry more than behavior. My PRDs for anything on a payment path encode non-functional requirements explicitly:
invariants:
idempotency:
key: provider_ref + attempt_epoch
namespace: ours
ttl: 24h
on_collision: return prior result, do not re-execute
ordering:
transport_guarantee: at-least-once, unordered
code_may_assume: nothing
failure_modes:
timeout: ambiguous, reconcile, never blind-retry
poison_message: DLQ after 3, alert, never block partition
partial_settlement: valid terminal state, requires ledger entry
reconciliation:
cadence: every 15m against provider ledger
drift_threshold: 0, any delta pages
observability:
trace: payment_id propagated end to end
Enter fullscreen mode Exit fullscreen mode
And verification has to be adversarial rather than confirmatory. Example-based tests written from the same understanding that produced the code will faithfully confirm that understanding, including the parts that are wrong. Property-based tests, fault injection, and contract tests are ground truth an agent cannot talk its way past. They are what make self-correction meaningful instead of self-congratulatory.
What actually got better
Being specific, because "10x productivity" is a claim I don't believe from anyone:
- Ambiguity surfaces before implementation, not after. The clarifying-questions pass is the highest-leverage part of the workflow and costs about four minutes.
- Wall-clock time on multi-slice features dropped substantially, mostly parallelism, plus not context-switching myself.
- The spec exists. For the first time in my career, the design doc isn't a thing we wrote after shipping to satisfy an auditor. It's load-bearing, so it gets maintained.
- Onboarding is different. A new engineer reads the PRD chain and understands why, not just what.
- My worst days got better. Tedious, well-understood, mechanically tiresome work now happens without my attention.
Now the honest part: I moved the bottleneck, I did not remove it
This is the section I most want people to sit with, because the industry conversation is stuck on generation speed, and generation speed stopped being the interesting variable a while ago.
Code review does not scale with code generation
Generation is now nearly free. Comprehension is exactly as expensive as it was in 2019.
A senior engineer can meaningfully review some finite amount of code per day. The number is smaller than any of us admit, and it collapses further when the reviewer didn't write the surrounding context. Point an agentic workflow at that reviewer and you don't get a linear slowdown, you get rubber-stamping, which is strictly worse than no review, because it manufactures false confidence and diffuses accountability across a team that now collectively believes someone looked.
What helps, in order of effectiveness for me:
- Vertical slices keep the review surface human-sized. A 200-line slice with a clear spec is reviewable. A 2,000-line feature drop is theater.
- Review against the spec, not the diff. The question is "does this satisfy the stated invariants," not "would I have typed this." The second question is unanswerable at volume and mostly ego anyway.
- A reviewing agent with a different context than the implementing agent. Same context reproduces the same blind spot. Different context, derived from the PRD rather than the implementation, catches things.
- Hard human gates where being wrong is expensive. Non-negotiable, no exceptions for velocity.
The answer is agentic validation, not more human eyes
If generation is agentic and verification is manual, you have built a funnel that ends at a person.
The only structurally sound response is to make verification agentic too: test generation derived independently from the spec, fault injection, contract conformance, invariant checking, spec-conformance review as a distinct automated pass.
The humans move up the stack. You review the specification and the invariants. Agents review conformance to them. That's a real change in what the job is, and I don't think it's a demotion.
The backlog moved downstream, and that is the actual story
Here's the thing nobody put in the launch demo.
Theory of constraints has been telling us this for forty years: you do not eliminate a bottleneck, you relocate it. Code was the constraint. Now it isn't. So go look at where it went.
BEFORE AFTER
product ▓▓ product ▓▓
eng ▓▓▓▓▓▓▓▓▓▓ ← here eng ▓
QA ▓▓▓ QA ▓▓▓▓▓▓
security ▓▓ security ▓▓▓▓▓
compliance▓▓▓ compliance▓▓▓▓▓▓▓▓▓▓▓ ← here now
ops ▓▓ ops ▓▓▓▓▓▓
Enter fullscreen mode Exit fullscreen mode
It went to QA, handed features faster than it can write test plans. To compliance, where PCI-DSS scope assessments, SOC 2 evidence collection, and data protection reviews run on a human cadence that did not just get 5x faster. To security review. To SRE and ops, absorbing a deployment frequency sized for a slower team. To change advisory boards, release trains, and every governance process that assumed engineering was the slow part.
If you make engineering agentic and change nothing else, you have built a machine that generates work-in-progress inventory for teams downstream of you. WIP is not throughput. In some organizations it is a liability with a carrying cost.
The response is not to slow engineering down. It's to recognize that agentic practice is an organizational transformation wearing a developer-tooling costume:
- Compliance evidence generated continuously as a build artifact, not assembled in a panic before an audit
- Policy-as-code, with control mapping evaluated in CI
- Agent-drafted change records and risk assessments, human-approved
- Compliance and QA present at the PRD stage, requirements encoded up front rather than assessed as a veto at the end
That last bullet has the best return and the most organizational friction. Shifting compliance left is far easier when the spec is already a machine-readable artifact your agents produced. It's one of the genuinely underrated second-order benefits of spec-driven work.
And a few costs I have not solved (click to expand)
Where does judgment come from now? If agents do the mechanical work, junior engineers don't get the reps that used to produce senior engineers. I don't have an answer. I don't think the industry does either.
Spec drift. A PRD that no longer describes the system is worse than no PRD, because agents trust it.
Cost. Four parallel agents burn tokens and CI minutes. Often worth it. Never free.
Ownership. Who is on call for code nobody wrote? The answer has to be a person, and the workflow has to make that person feel it.
Nondeterminism. Same prompt, different output. Your process must be reproducible even when generation isn't, which is precisely why the spec, the slices, and the gates are the durable parts, and the generation is the fungible part.
What I actually want you to take away
The shift is not that a model can write your code. It's that your job moved from authoring the system to designing the system that authors the system.
The skills you're compensated for are increasingly: specification, decomposition, invariant definition, verification design, and knowing where a human must stand in the loop. That's architecture work. It was always the valuable part. It's now the only part that doesn't commoditize.
So, two things to go do this week:
1. Find your most repetitive class of work, the feature shape you've built eleven times. Don't write a code-generation skill for it. Write the specification skill first. Make it ask you clarifying questions. You'll be unsettled by how many you can't answer.
2. Go measure where your constraint actually is right now. Not where it was when you last thought about it. If your engineers ship in two days and your compliance review takes three weeks, then every hour you spend making generation faster is an hour spent making the queue longer.
Make it agentic end to end, or don't be surprised when the bottleneck simply moves in next door.
I write about distributed systems, payment infrastructure, and agentic engineering practice. If you're running a variation of this workflow, especially in a regulated environment, I want to hear what broke. That's the most useful thing anyone can share right now.
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.