Skip to content
8 min read

Vibe engineering demands more discipline, not less

Vibe engineering turns AI-generated code into maintainable systems through explicit contracts, focused tests, bounded tools, and evidence-based review.

Vibe engineering demands more discipline, not less
Table of Contents

Vibe engineering is disciplined software development with an AI doing much of the typing. The engineer still owns the behavior, the boundaries, and the evidence that the change works. Remove that ownership and the same fast feedback that feels productive during a demo turns into an expensive cleanup cycle after release.

I use the term to draw a hard line between two practices that often get lumped together. Vibe coding accepts plausible output and keeps moving. Vibe engineering uses the model as a fast implementation partner inside a controlled system. The prompt matters, but the repository, tests, review rules, permissions, and production feedback matter more.

That distinction explains why two teams can use the same model and get opposite results. One team ships small changes that another engineer can understand. The other accumulates duplicated helpers, accidental API changes, permissive error handling, and tests that merely confirm the implementation it just generated. More prompting skill will not repair a weak engineering loop.

Vibe engineering starts with an executable contract

The first discipline is to define observable behavior before asking the model to change code. A ticket that says "add retry support" leaves every consequential decision open: which failures qualify, how long the caller waits, whether the operation is idempotent, what gets logged, and when the system gives up. An agent will fill those gaps with familiar patterns. Familiar is not the same as correct for your system.

Write a compact task contract that names inputs, outputs, invariants, forbidden changes, and proof. Keep it beside the work so both the agent and reviewer see the same boundary. This example is deliberately plain:

task: retry invoice delivery
behavior:
  retry_on: [timeout, connection_reset, http_502, http_503]
  attempts: 3
  max_elapsed_ms: 8000
invariants:
  - reuse the existing idempotency key
  - never retry http_400 or http_401
  - preserve the public function signature
proof:
  - unit tests for each retryable status
  - integration test proving one invoice is created
  - log field retry_reason contains no request body

This contract prevents a common failure I have seen in AI-assisted changes: the model adds a generic retry library, wraps every exception, and turns a deterministic authorization failure into three identical requests. The code looks clean. The behavior is wrong. A reviewer who only scans syntax may approve it because the missing decisions never appeared in the ticket.

An executable contract does not mean a long specification. For a one-line validation fix, two acceptance examples may be enough. For a payment path or data migration, the contract needs failure behavior, rollback conditions, and ownership. Scale the contract with the cost of a wrong assumption, not with the number of lines you expect the agent to write.

Ask the agent to restate the contract and identify ambiguities before editing. If its restatement differs from yours, stop there. Ten minutes spent resolving whether "duplicate" means identical bytes or the same business identifier saves hours of inspecting a polished implementation of the wrong rule.

AI speed increases the size of unchecked mistakes

AI-generated code is risky because it can expand an untested assumption across many files before a human notices. The model does not need to hallucinate an API to cause damage. It can apply a reasonable local pattern in a place where a hidden system constraint makes that pattern unsafe.

Consider a developer asking an agent to replace integer status values with an enum. The agent updates the model, serializers, handlers, fixtures, and tests in one pass. Everything compiles. But an old mobile client still sends numeric values, and a reporting job reads the raw database column. The change is internally consistent while breaking two external contracts. The speed and consistency make the mistake harder to spot because reviewers see fewer obvious loose ends.

This is why generated line count is a useless productivity measure. A large coherent diff can consume more review time than the model saved. The useful unit is a verified behavior change: a narrow result with evidence, a known blast radius, and a rollback path.

DORA's research on AI-assisted software development describes AI as an amplifier of an organization's existing strengths and weaknesses. That matches what I see in working repositories. Clear ownership, small batches, reliable tests, and quick review get faster. Ambiguous product decisions, brittle builds, and weak observability also get faster, but in the wrong direction.

Set a diff budget before the run. It need not be an inflexible line limit. Define the expected files and stop conditions: no new dependency without approval, no public interface changes, no schema edits, and no unrelated formatting. If the model discovers that the request requires crossing one of those lines, it should report the finding and wait. That pause is useful engineering information, not agent failure.

Small batches also make model drift visible. After one behavior change, inspect the diff and run the focused checks. Then continue. A single prompt that asks for a new endpoint, database migration, cache, metrics, and documentation creates five places where an early misunderstanding can spread. Breaking that request into verified slices is usually faster than reviewing the resulting tangle.

Repository context must be explicit and current

An agent can only follow local conventions that it can find and interpret, so the repository must state the rules that people otherwise carry in their heads. Telling the model to "follow best practices" imports generic preferences. Telling it where authorization lives, which module owns transactions, how errors cross the API boundary, and which commands gate a merge gives it usable constraints.

Start with a short repository instruction file. Name the architecture boundaries, canonical commands, generated directories, dependency policy, and areas that require human approval. Keep the instructions close to the code they govern. A service-specific file should override a broad root rule when the service has a different test runner or deployment constraint.

Do not turn this file into an employee handbook. Agents lose important constraints inside long prose just as people do. Write commands exactly as they run and explain unusual rules with the failure they prevent. "Do not call the database from HTTP handlers because transaction retries live in the service layer" gives both a boundary and a reason. The agent can then inspect the service layer instead of inventing another pattern.

Documentation that disagrees with the repository is worse than missing documentation. Add instruction files to code review ownership, test their listed commands in continuous integration where practical, and remove dead examples. If the standard test command needs a secret that new contributors cannot obtain, the documented path is fiction. AI will expose that fiction more often because it repeatedly tries the advertised path.

Context also includes negative knowledge. Record abandoned approaches when they remain tempting: the cache library removed after inconsistent invalidation, the client generator that cannot represent a union type, or the database feature unavailable in production. Without that note, an agent may confidently reintroduce yesterday's incident cause under a tidy abstraction.

Let the agent explore before it edits. Ask it to locate similar behavior, identify the call chain, name the tests that cover it, and list the files it expects to touch. This is not ceremony. Its answer shows whether it found the authoritative implementation or latched onto an obsolete example. Correct the map first, then authorize the change.

Tests must attack behavior rather than mirror the patch

Tests generated from the same prompt as the implementation often repeat the same misunderstanding, so they cannot be the only proof. If the prompt says a token expires after 60 minutes when the actual rule is 30 minutes of inactivity, the agent can generate code and tests that agree perfectly on the wrong contract.

Separate acceptance examples from implementation generation. Write the highest-risk examples first, or ask another pass with fresh context to derive tests from the task contract and public interface. The goal is independent pressure, not a ritual where every new method gets a mock-heavy unit test.

For the retry contract above, useful tests exercise a timeout followed by success, exhaustion after three retryable responses, immediate failure on an authorization response, preservation of the idempotency key, and elapsed-time enforcement. A test that asserts the retry helper was called three times binds itself to the current structure while missing the customer-visible behavior.

Run the narrow test, then the surrounding suite, then static checks. Capture commands and their result in the work record. A credible agent report looks like this:

$ make test-invoice
42 passed, 0 failed
$ make lint
0 errors, 0 warnings
$ make verify-format
format checks passed

The exact tools vary, but the evidence shape should not. "Tests pass" is a claim. The command, scope, exit result, and any skipped checks are evidence. If the agent cannot run an integration test because a service is unavailable, it should say so plainly. A reviewer can then decide whether to provide the service, run the check elsewhere, or reject the change.

Mutation testing, property tests, and fuzzing can help on parsers, financial rules, and state machines because they challenge the implementation beyond examples the model anticipated. They are not mandatory for every patch. Use them where the input space or invariant matters more than a few known cases.

NIST's Secure Software Development Framework separates review of human-readable code from testing executable code and includes both automated analysis and human review. That distinction matters here. A clean static analysis run does not prove business behavior, and a passing end-to-end test does not reveal an unsafe query assembled in a rare branch. Vibe engineering needs both kinds of evidence, chosen for the risk.

Review the diff without trusting its author

Shrink diffs and payroll together
I redesign the delivery loop so a smaller AI-augmented team can ship verified changes.

Code review for AI-assisted work should assume the explanation may sound better than the implementation. Models are good at producing a coherent summary of what they intended. Review the actual diff, then compare the summary with it. If the summary claims input validation but the code only checks for a nonempty string, trust the code.

Begin at the contract boundary. Check public types, API payloads, database schemas, events, files, and environment variables before reading internal helpers. Most expensive surprises hide at those edges. Then trace one success path and at least one failure path through the changed code. Confirm who catches errors, what state remains, and what a caller observes.

Review generated tests with the same suspicion as generated production code. Look for assertions that can pass without proving anything, mocks that replace the component under test, snapshots updated without inspection, broad exception expectations, and deleted cases. Agents sometimes make a red suite green by weakening the test rather than fixing the behavior. The diff shows that move if the reviewer looks at test deletions and assertion changes first.

Dependency additions deserve a separate decision. A model may reach for a package because it has seen the API often, not because the package fits your support, license, security, or bundle constraints. Require the change to justify why existing code or a current dependency cannot do the job. Do not let a five-line convenience import silently become a production maintenance obligation.

The human review should focus on product intent, architecture, security boundaries, and operational consequences. Formatting, type checking, known unsafe calls, and basic test execution belong in automation. Making a person inspect issues a deterministic tool can reject trains reviewers to skim. Save human attention for decisions the tool cannot understand.

For high-risk changes, use a second agent as a critic, but do not treat agreement between two models as independence. Give the critic the contract and diff, withhold the first agent's rationale at first, and ask for concrete counterexamples. A useful finding names an input, state, or sequence that breaks the claimed behavior. General praise or vague concern adds no evidence.

Tool access must match the cost of a wrong action

Agent permissions should expand only as the task proves it needs them. Read access to the repository, writes on a branch, test execution in an isolated environment, and production operations carry different consequences. Putting them behind one broad approval turns a coding assistant into an operator before the team has designed operator controls.

Default to a disposable worktree or container with no production credentials. Allow commands through an explicit policy, block secret files, and log tool calls. Package installation, network access, migration execution, infrastructure changes, and destructive database commands should require a human decision or a tightly scoped automated gate.

Approval prompts alone do not solve the problem. A developer who sees twenty routine requests will start approving by rhythm. Group predictable safe actions into a reviewed policy and reserve interruptions for meaningful boundary crossings. The prompt should explain the exact command, target, and reason. "Allow shell access?" is too broad to support a responsible choice.

Secrets require special treatment because an agent can copy them into logs, fixtures, error messages, or prompts without malicious intent. Use short-lived credentials with the minimum scope, inject them only into the process that needs them, and scan diffs plus captured output. Never place a production secret in a repository instruction file or conversation context.

Treat retrieved text as untrusted input. An issue, dependency README, generated file, or web page can contain instructions that conflict with the task. The agent should use that content as data, not authority. Repository policy and explicit user direction must outrank text discovered during exploration.

Production access belongs in a separate workflow with stronger identity, a narrow command set, dry-run support, and a rollback plan. The person approving a deployment should see the tested artifact and intended change, not simply the agent's assurance. Autonomy can grow after the team has evidence about a repeated, bounded task. It should not grow because the demo looked smooth.

Legacy systems require narrower agent boundaries

Stop paying for generated rework
The five-day audit identifies at least $50,000 in annual savings or it is free.

Vibe engineering can work in a legacy system, but the agent should receive a smaller change surface and more observational evidence than it would in a well-tested service. Old code often contains business rules that nobody documented and external dependencies that no local test reproduces. A model sees irregular code and tends to normalize it. Some of those irregularities are defects, while others are compatibility constraints paid for by an incident years ago.

Begin with characterization, not cleanup. Capture what the system does at the boundary before changing how it does it. For a file importer, save representative inputs and exact parsed outputs. For a billing job, run a scrubbed production sample through the current version and record the resulting ledger entries. For an old API, capture status codes, response fields, and side effects for known callers. These fixtures do not declare the current behavior correct. They reveal which behavior the proposed change would alter.

This makes a useful distinction between preservation tests and requirement tests. A preservation test says the new implementation matches the old one for a chosen case. A requirement test says the business wants a particular result. When the two disagree, a person must decide whether the old behavior is a bug, an undocumented contract, or both. Asking an agent to "fix" the disagreement without that decision simply hides a product choice inside generated code.

Do not start by asking the model to refactor the entire module so the requested feature becomes easier. That recommendation is popular because the existing code is unpleasant and the model can produce a cleaner shape quickly. It is wrong when you cannot explain all callers and side effects. Make the behavior change through the smallest safe seam, prove it, and refactor only the parts whose responsibilities you now understand.

Legacy data deserves even tighter control. An agent can write a syntactically valid migration that locks a large table, rewrites values irreversibly, or assumes every historic row follows today's validation rules. Require a read-only inventory first: null counts, distinct legacy values, affected row shape, index coverage, and an estimate obtained from a representative environment. Review the forward migration and recovery procedure as separate artifacts. A rollback that tries to reconstruct discarded data is not a rollback.

When tests are sparse, compensate with multiple signals rather than declaring the area unchangeable. Add focused characterization tests, compare old and new outputs, replay sanitized traffic, inspect query plans, and release behind a narrow cohort or feature control where the architecture supports it. Watch business outcomes and error logs during the rollout. None of these signals alone proves safety, but together they make assumptions visible.

The agent's exploration report matters more in this environment. It should name entry points, runtime configuration, scheduled jobs, data stores, outbound calls, and code paths it could not trace. An honest unknown is useful. A confident diagram inferred from filenames is not. If a critical caller remains unexplained, reduce the task or find someone with the missing operational knowledge.

Legacy work also exposes the cost of context loss. If the person who understands month-end reconciliation reviews only the final diff, the agent may spend hours building around a false assumption. Bring domain review forward to the contract and examples. Use engineering review later for the implementation. That ordering spends scarce human attention where it can prevent the largest wrong turn.

A successful legacy change should leave one part of the system easier to reason about. Keep the new boundary test, document the odd compatibility rule, delete the obsolete path only after usage evidence supports it, and record how to observe the behavior in production. The goal is not to make the whole codebase modern in one pass. It is to make this change safe and leave reliable evidence for the next one.

Maintainability depends on leaving a legible repository

Replace headcount without losing ownership
I design the path from a ten-developer team to one or two AI-augmented engineers.

Maintainable AI-assisted code looks boring to the next engineer. It uses existing concepts, puts behavior in the expected layer, has tests that describe the rule, and avoids abstractions created only to make one generated patch appear elegant. Novelty raises the amount of context every future agent and human must load.

Ask whether the change reduces or increases the repository's vocabulary. If one module calls a customer an account, another calls it a tenant, and the patch introduces workspace, the model may have built three clean components around one confused concept. Rename toward the domain term instead of documenting the synonyms.

Duplicated code needs judgment. Two explicit blocks may be easier to maintain than a generic framework with callbacks, options, and type parameters. Agents often abstract early because common training examples reward deduplication. Wait until the shared behavior and its variation are clear. A small amount of duplication is cheaper than the wrong abstraction copied across the system.

Comments should preserve decisions the code cannot express. Do not accept comments that narrate the next line or claim that a branch is "important." Record why the obvious approach fails, which external constraint applies, or what invariant a strange sequence protects. Update the task contract or architecture note when the decision will matter beyond this patch.

Keep generated changes inside normal ownership. The engineer who approves a module owns it after merge, regardless of who typed it. There is no useful category called "AI code" that can carry lower standards or wait for a future cleanup sprint. If nobody on the team can explain a critical path, the change is not ready.

This is also where aggressive team reduction can backfire. A small AI-augmented team works when its engineers understand the system and maintain strong feedback loops. Removing domain knowledge first and expecting agents to reconstruct it from a messy repository produces fast motion with slow recovery.

Measure verified delivery and rework

The useful business case for vibe engineering is shorter time from a clear decision to verified production behavior. Measure that path, including review, failed checks, rollbacks, and follow-up fixes. If you count only prompt-to-pull-request time, you reward code generation while hiding the cost transferred to reviewers and operators.

Track a small set of operational signals by change type: lead time, review time, escaped defects, rollback or hotfix rate, and rework within the next few changes. Compare similar work before and after adopting the workflow. A generated documentation fix and an authorization redesign should never share one productivity bucket.

Do not use acceptance rate as a quality metric. A developer can accept a large suggestion and spend an hour repairing it. Another can use the model to investigate, reject its patch, and make the right three-line change. Repository outcomes matter more than how much model output survives.

Review queues expose system limits quickly. If agents create pull requests faster than experienced engineers can verify them, adding more generation makes delivery slower. Limit work in progress, reduce change size, and invest in deterministic checks. The goal is not to keep the model busy. The goal is to move trustworthy changes through the whole system.

Run a monthly sample review of merged AI-assisted changes. Pick successes, ordinary patches, and incidents. Check where the task contract was incomplete, which tests caught useful failures, what reviewers missed, and whether repository instructions were current. Turn repeated findings into a test, policy, template, or architecture fix. Training alone fades; repository controls persist.

A Team & AI Audit can identify where an engineering workflow can safely compress headcount and where missing tests, ownership, or production controls would make that move reckless. The point is to find savings in the whole delivery system, not to produce more code with fewer people.

Vibe engineering is worth adopting when it improves verified throughput without pushing hidden work downstream. Give agents precise boundaries, independent checks, limited tools, and a repository that tells the truth. If a team cannot produce evidence for a change, the model has finished typing, but the engineering is not finished.

Frequently Asked Questions

What is vibe engineering?

Vibe engineering is software development where AI performs much of the implementation inside explicit technical and operational controls. A human still owns the contract, reviews the change, checks the evidence, and accepts the production consequences.

How is vibe engineering different from vibe coding?

Vibe coding optimizes for getting plausible software quickly and often accepts output by feel. Vibe engineering optimizes for verified behavior, maintainability, and controlled risk, even when the model writes the same amount of code.

Is AI-generated code safe for production?

It can be, but its origin does not make it safe or unsafe. Production readiness comes from a clear contract, risk-appropriate tests, review of boundaries and failure paths, restricted tool access, observability, and a rollback plan.

Should every AI-assisted change receive human review?

A human should review product intent and risky consequences until a narrowly defined change type has earned a stronger automated path. Teams can automate routine checks, but they should not confuse a passing tool with accountable approval.

How large should an AI-generated pull request be?

Keep it small enough that a reviewer can trace the behavior and failure path without reconstructing the whole system. Use expected files and forbidden boundaries as a budget rather than relying on a universal line count.

Can an AI agent write its own tests?

Yes, but those tests cannot be the only proof because implementation and tests may share the same mistaken assumption. Anchor tests in independently written acceptance examples and inspect whether each assertion proves public behavior.

What should go in repository instructions for coding agents?

Include exact build and test commands, architecture boundaries, generated directories, dependency rules, forbidden actions, and areas requiring approval. Keep the file short, current, and specific to failures your repository can actually suffer.

Which metrics show whether AI-assisted development works?

Measure lead time through production, review time, escaped defects, rollbacks or hotfixes, and subsequent rework for comparable change types. Prompt-to-pull-request speed and accepted line count hide too much downstream cost.

Can a small team maintain a large AI-built system?

A small team can maintain a large system when it retains domain knowledge, keeps architecture legible, and has strong automated and production feedback. AI does not compensate for absent ownership or a repository that contradicts its documentation.

When should an AI coding agent get production access?

Only through a separate, tightly scoped workflow after a repeated task has clear controls and evidence. Use narrow credentials, explicit commands, dry runs, logging, human approval for consequential actions, and a tested rollback path.

Related Posts