The agent writes the code either way. What separates a weekend prototype from a system people depend on is everything around the agent: the rules it reads first, the checks that run before anyone looks, the gates a change must pass to reach a server. This article is about that difference, why I think the second practice is where software engineering is going, and what the machinery looks like.

Two practices with the same keyboard

Vibe coding has a precise meaning, the one Andrej Karpathy gave it when he named it in February 2025. You describe what you want, accept what comes back, do not read the diff, and when something breaks you paste the error into the chat. The code is a side effect of a conversation. For a throwaway tool or a prototype to show on Thursday, this is the right amount of ceremony, which is to say none.

I am a fan of vibe-coded prototypes myself, with one strict rule: you harvest ideas from them, never code. A prototype is how you find out what to build. The feature that ships gets built again, inside the guardrails.

Agentic engineering uses the same agents, often the same prompts, and a different contract. The agent still types and runs things, but inside the system a senior team would build for human engineers: a written standard, strict types, a linter that blocks on warnings, tests at several depths, a pipeline that decides what reaches staging and production, and a review that reads evidence rather than keystrokes. The engineer’s job moves up a level: less writing code, more designing the machinery that decides whether code is right.

The difference is not how much the human types. It is who decides that a change is true. In vibe coding, the human’s eyes and mood at the moment they look. In agentic engineering, a set of checks that run every time, for every agent, whether or not anyone is watching.

Area
Vibe coding
Agentic engineering
Who decides
The human eye, in the moment
Checks that run on every change
Rules
Repeated in prompts, gone next session
In the repository, read every session
Scope
Drifts with every request
Owned files, deviations reported
Tests
Run when someone remembers
Unit to end-to-end, in the pipeline
Review
The chat, if anyone reads it
A structured result: green, gaps, deviations
Deploys
When it looks done
Through gates that ask the pipeline
The engineer
Prompts and pastes errors
Designs the checks, keeps two human gates

Where the vibe stops working

Vibe coding fails in a way that is easy to miss, because the early days feel wonderful. Three things happen as the codebase grows.

Confidence is uniform. An agent reports a working feature and a broken one in the same tone. Without an independent check, the only way to tell them apart is to test by hand, and nobody tests by hand twice a day for a month.

Scope drifts silently. Asked for a form field, the agent also “improves” the validation beside it, renames a type and bumps a dependency. Each change is defensible. Together they are a pull request nobody can review and a behaviour change nobody asked for.

Invariants erode. Every colour comes from a design token, a column is never read without its access check, a migration never runs on a branch. A human team keeps these in their heads and their reviews. An agent keeps only what the repository enforces, because every session starts from zero.

None of these is a model problem. Newer models make each change better, and the three failure modes stay where they were, because they belong to the process, not the code.

Why the guardrails are the future

The argument comes down to where the bottleneck moves.

Code generation is cheap and getting cheaper. When writing is nearly free, the expensive part of engineering is knowing whether what was written is correct. A team that verifies by hand can run one agent and watch it. A team whose verification runs itself can run several agents in parallel, in separate worktrees, and read their results in the evening. The guardrails turn a faster typist into a larger team.

Guardrails also compound in a way that review does not. A lint rule written once applies to every future change by every future agent; a gate that refuses a deploy without a green pipeline refuses it at three in the morning as well. Each rule is a sentence of the specification that enforces itself, and agents are the ideal audience: they actually run the command, every time, without deciding that this one small change does not need it.

And agents write guardrails well: the session that builds a feature can add the test that pins it and the lint rule that prevents the regression. A human skips those on a busy afternoon. An agent told that a feature is not done until its acceptance commands pass does not have a busy afternoon.

What a serious project looks like

Guardrails earn their keep on a serious project, not a weekend one: a product that runs every day, keeps changing for years, and holds more code than anyone can keep in their head. A typical shape: a public portal and a back office, both Angular, talking over GraphQL to one API on Node that also serves their images, with a database behind it, outgoing mail, third-party APIs and a live hub over WebSocket for anything that must update without a reload. One monorepo holds it all, with a shared component library and the test suites. On a project like this a change can break a screen nobody has opened in a month, and a migration runs against data that cannot be lost. What follows is the machinery that lets agents work on it safely, layer by layer.

The rules the agent reads first

Every project in the monorepo has an instructions file, and the root one links to what an agent needs before touching anything: the coding standard, the testing strategy, the design rules, the release procedure. They are short, specific and written for a reader with no memory of yesterday.

Lines in this register work well: “The type system is the cheapest test suite.” “A flaky test is a failing test.” “Over-commenting is a common LLM failure mode: restated logic, step-by-step narration, doc comments on internal functions.”

One habit matters more than any single rule: decisions are recorded where they apply, with who made them and when. A workflow that deliberately fails a job without blocking a deploy says so in a one-line comment with a name and a date, and the agent that reads it next month does not reopen the question.

Type safety and static analysis first

The cheapest guardrail runs before a single test exists. Strict types turn whole classes of mistakes into errors the build refuses: a value that might be missing, data in the wrong shape, a field renamed in one place and forgotten in another. Static analysis does the same for the rules a team cares about, and it blocks: a warning is a failure, not a suggestion. Module boundaries belong here too, so a UI library cannot reach into the API and one feature cannot lean on another’s internals.

For an agent this is the best feedback there is: immediate, precise and impossible to argue with. It reads the error, fixes the code and runs the check again, long before a reviewer would have spotted the problem. Exceptions still happen, but each one is a visible, narrow and explained decision instead of a quiet escape hatch.

Tests at every depth

All of them run in the pipeline, never only on a laptop.

  • Unit tests on pure logic: permission grids, date and money formatting, parsers, the release tooling itself.

  • Integration tests against a real database, created fresh for every run, with third-party services and sign-in replaced by recorded stand-ins. Some walk the whole data model and fail when a protected field has no access rule, so new data cannot be exposed by forgetting it.

  • Component tests that render each piece of the interface in a real browser, click through it and check that it stays accessible.

  • End-to-end tests that use the whole application the way a person would, split across parallel runners, each with its own database, inbox and servers.

Where no ready-made tool covers a rule, a small test can scan the repository instead, so a team convention becomes a check rather than a review comment. And the flake policy is written down: a test that fails once under load is rerun alone, and a test still flaky after that is treated as failing.

The pipeline shape

The main pipeline is a set of independent jobs: tests at every depth, the build, static analysis, formatting and code generation. That last job regenerates everything derived from a schema, such as the API and database types, and fails if anything changed. It is the only honest check for generated code: not “is it formatted” but “is it what the schema says”.

Pull requests run what they affect; pushes to main run everything. A separate check enforces the changelog: every change users will notice comes with a plain-language note, or says explicitly that it needs none.

Deploy gates that ask the pipeline

Staging deploys after a green run on main, but “green” is decided by a small shared check that asks the CI itself instead of trusting a glance at it: the commit must be the latest on main, its own run on main must have passed, and the only exceptions are on an explicit, dated allow list for a flaky browser suite that should not hold staging hostage. Deploys queue behind each other instead of cancelling, so a database migration is never cut off halfway by the next push.

Every deploy ends with a smoke test that proves the system is wired, not just that a page came back: the application loads, the API answers real queries, each kind of request reaches the service it should, and staging stays hidden from search engines.

Production is stricter and manual. A human tries the release on staging by hand, tags it and starts the run, and the gate refuses anything that is not a vYYYY.M.X tag (year, month, and X a number incremented with every release) whose version matches the code, sits on main and has a full, successful integration run with no allow list. Then it migrates the database, deploys and smoke-tests the API, deploys and smoke-tests the applications, and publishes the release notes from the tag.

The agent’s own rules

The layers above would protect a human team equally well. The last layer exists because the engineers are agents.

Work is split into groups, each in its own git worktree and branch, each with a list of files it owns and a list it must not touch. A group reports a structured result, not prose: green is true only if every acceptance command passed on the final tree, and the result also carries deviations from the spec, unfinished items and a section written for the human. Every group’s output goes through an adversarial review before integration, with lenses for the seams between groups and for whether the end-to-end tests still describe the feature. Instructions like these work: “Report nothing you cannot show a failure for. An empty findings list is a welcome result.” “A reported gap is worth far more than a claim of green.”

Then the hard limits. History stays linear, with no merge commits. The commit hooks are never skipped. Nothing is pushed without the human’s say. Workflow changes are committed separately and held back until the human has read them. At the end of a phase the agent writes a recap and stops.

Supply chain and the boring parts

Dependencies install with a minimum release age, so a package published this morning cannot enter the tree until the ecosystem has had a look at it, and with a trust policy that refuses a version published with weaker provenance than the one before. Install scripts are blocked by default and allowed one package at a time, with a comment on each. Container images and runtime versions are pinned. None of this is clever, and all of it is what an agent will quietly undo when told to “just make the install work”, unless the policy is in a file it cannot argue with.

What the guardrails cannot catch

A guardrail list only checks what someone thought to write down. It cannot tell you that the feature is the wrong feature, that the spec was misread in a way the tests share, or that the result is unpleasant to use. Those still need a person who tries the thing. Most teams also carry the same gaps: coverage measured but nothing failing when it drops, no security or licence scanning, no screenshot diffing, a rollback that is a hosting-console gesture rather than a tag. Keep that list in the repository, and let the same tooling that ships features audit the pipeline and write down what it would not catch.

How to get there

An order that works, starting from a repository with some tests and a CI that runs them.

Make what you have blocking. Warnings become errors. Formatting runs in a commit hook. The pipeline is required, not advisory. This changes the agent’s behaviour immediately, because it runs the commands and reads their exit codes.

Write the instructions file. One page the agent reads first, linking to the standards that exist. Record decisions in it with a name and a date. Add the rule that a feature is not done until the acceptance commands pass, and name the commands.

Add a drift check for everything generated. Types from a schema, clients from an API, a lockfile from a manifest. Regenerate in CI and fail on a diff.

Give the deploy a gate that reads the pipeline. Staging after green on main. Production from a tag, by hand, after a full run. Smoke-test every deploy with checks that prove the system is wired.

Give the agent a result format. A boolean it may set only when the commands passed, a field for what it did not finish, a field for what it changed that the spec did not ask for. Review the fields, not the transcript.

Keep the two human gates. Pushing to main and running production. Everything else can run itself.

A prompt that can start this on an existing repository:

Reviewing the gate it proposes is a ten-minute read. Writing one by hand is an afternoon that rarely comes.

Where this lands

Vibe coding is real and useful, and it is a description of a mood, not of a method. When the result has to run for people who depend on it, the agent is still the right thing to have typing, and the engineer’s job becomes the system around it: the rules the agent reads, the checks that run unasked, the gates that decide what reaches a server, the two decisions that stay human. That system is what lets one engineer run several agents and sleep. It is also, not by coincidence, what a good engineering team was always trying to build for itself. The agents just made it mandatory.