Most of the code in my projects now gets typed by an AI agent. I still consider every line mine, and this post is about how I keep that true. The short version: I orchestrate, the agent executes, and I guide every step so the result is accurate, understood, and ready for someone else to depend on.
Agents can write most of a typical feature. That changed what I spend a day on. It did not change what I'm for. Someone still has to understand the problem, know the system, decide what good looks like, and answer for the result when it reaches a person. When nobody holds that job, you get vibe coding. I'd rather hold it on purpose. I've been calling the way I work agentic engineering: I'm the orchestrator and the decision maker, the agent is the executor, and the work is arranged so I can guide it, check it, and stand behind it.
Vibe coding, and why I don't ship it
The phrase is Andrej Karpathy's, from early 2025. Talk to the model, accept whatever it produces, paste errors back in when something breaks, stop reading the code. He was clear that it suited throwaway weekend projects, and for a weekend project it's a fine way to work. I've done it. It's fun.
The trouble starts when the same habits reach software somebody depends on. The failure pattern is consistent:
- Nobody can explain the code. It works until it doesn't, and then debugging becomes archaeology.
- Small misreadings compound. A wrong assumption in minute two becomes a wrong file in minute five and a wrong data model by the end of the session.
- "It looks right" gets treated as evidence. Output that compiles and demos cleanly ships with invented behavior, missed conventions, and reinvented utilities.
- Scope drifts with the conversation. The agent goes where the chat goes, and nobody is steering.
None of this is the tool's fault. It's the absence of an operator.
The one principle
The discipline is simple to state: a developer who understands the work directs an agent that does the work. I own the decisions: what to build, how it fits the existing system, what done means, and what ships. The agent owns the labor: retrieving context, drafting the plan, writing the code, generating the tests, and explaining itself along the way.
Everything else follows from one principle.
Execution quality is proportional to context quality and plan quality.
Give an agent curated context and a sharp plan and it will usually implement a whole feature correctly in one pass. Give it a vague request and a blank slate and it produces something plausible and wrong. The speed doesn't come from typing faster. It comes from the rework I never do, because the plan was right before the first line was written.
| Vibe coding | Agentic engineering | |
|---|---|---|
| Who decides | The conversation | Me |
| Unit of work | Whatever comes up | One behavior, one hour, one pull request |
| Before code | Start typing | Load context, write and refine a plan |
| During | Walk away, accept all | Watch every action, redirect at first drift |
| After | Ship if it runs | Review the diff against the plan, run it, verify it |
| When it breaks | Paste the error back in | Understand it, because I approved every line |
Who does what
Me, the orchestrator
- Context curator. I choose what the agent sees: the ticket, related code, schemas, house style, prior decisions. Curated, not dumped.
- Architectural reviewer. I check the plan against the system as it actually is, not as the ticket assumes.
- Implementation guide. I watch execution and redirect the moment it drifts.
- Quality controller. I own the diff, the tests, and the decision to ship.
The agent, the executor
- Retrieves and summarizes context faster than I can read it.
- Drafts the plan and revises it on feedback.
- Implements, writes tests, runs them, and reports what happened.
- Explains its reasoning so I can check it.
What I never delegate
- Scope. What's in, what's out, and what waits.
- Architecture and data model. Including whether an existing path should be reused instead of a new one built.
- What done means. Acceptance criteria and how the result will be verified.
- What ships, and when.
- Anything destructive or outward-facing. Migrations, deletes, deploys, messages sent on my behalf. The agent proposes, I authorize, never the reverse.
The loop
One ticket at a time, five steps, repeated for every unit of work.
- Load the context. Everything the agent needs, nothing it doesn't.
- Plan before code. Research and design, nothing changes on disk.
- Refine until I'd sign it. Several rounds is normal.
- Watch and redirect. Stay in the room while it runs.
- Review before it leaves. Diff against the plan, then run it.
1. Load the context
Before anything gets planned, I give the agent everything it needs and nothing it doesn't. Pull the ticket with its comments and linked issues. Find the code the feature should mirror, the utilities it should reuse instead of reinvent, the test conventions to match, and any schema it touches. Add the house style guide and any prior analysis of the ticket.
Curate rather than dump. Whole files pasted in bulk bury the detail that mattered. Targeted retrieval from the codebase and the docs beats volume every time. A prompt for this step looks like:
Pull ticket 1432 with its comments and linked issues. Find the existing
code this feature should mirror, the utilities and services to reuse, the
test conventions and fixtures to match, and any tables or migrations it
touches. Then summarize the acceptance criteria, the files and functions
we will reuse, the edge cases, and anything the ticket asserts that the
live code contradicts. Do not plan or write code yet.
2. Plan before code
Most coding agents have a planning or read-only mode where they research and write but change nothing on disk. I use it every time. I ask for a plan I could hand to a colleague and have them execute in one pass.
A plan worth approving:
- Names the exact files and functions to touch, and the existing ones to reuse.
- Treats the ticket's claims as hypotheses and flags where the code or schema contradicts them.
- Traces the data to its downstream consumers before choosing a data model.
- Lists what's out of scope so creep is visible up front.
- Specifies the automated tests to write and a manual checklist to verify the result.
- States open questions instead of guessing.
Enter planning mode. Design the full implementation for ticket 1432 so it
can be executed in a single pass. Be specific about file paths and
function names. Reuse existing patterns and utilities rather than adding
abstractions. Verify the ticket's assumptions against the live code and
flag contradictions. Include the automated tests to generate, a
step-by-step manual QA checklist, an explicit out-of-scope list, and any
open questions where the ticket is ambiguous.
3. Refine until I'd sign it
The first plan is almost never the final plan. Three to five rounds of feedback is normal for a non-trivial feature. I push back in plain language: reuse this service instead of writing a new one, the relationship runs the other direction, this edge case is missing. If it's faster, I annotate the plan file inline and ask the agent to re-read it.
Confidence comes from the plan, not from the execution. Time spent here is the cheapest time in the whole loop. When I'd put my name on the plan, I approve it.
4. Watch and redirect
A well-planned execution is fast and usually correct. I stay in the room anyway. I read the actions as they stream: which files are being read, which are being edited, which commands are being run. The moment I think "why is it touching that," I interrupt.
- Redirect at first drift. A correction now costs seconds. The same correction after twenty more edits costs the session.
- Deny actions I don't understand. A plan I can't follow is a plan I can't supervise.
- Hold destructive and outward actions for approval. Propose, then authorize.
- Keep the session on the one ticket. Adjacent work gets its own session.
5. Review before it leaves my machine
The plan is the scope contract. I compare the diff against it. Changes the plan didn't promise are a flag, not a bonus.
- Read every line. If I can't explain it, I can't accept it.
- Run it where running is the test. Compiling is not behaving.
- Tests must actually execute, not merely exist.
- Run an automated review pass locally before opening a pull request, and dispose of every finding explicitly: fixed, declined with a reason, or deferred to a tracked item. Three or four rounds usually collapse to nothing left to flag.
Only then does the work go to human reviewers, who can read the plan to understand intent before they read the code.
How I brief an agent
Every prompt in the loop is a brief, and a good brief has the same five parts whether it asks for a feature, an analysis, or a review. Missing parts are where agents improvise.
- Role. Who the agent is for this task and what it's expected to know. A role sets the standard the output is judged by.
- Task. What to do, in order, with the source of truth named. "Retrieve, then determine, then rank" beats "help me with the board."
- Output format. The exact shape of the result: sections, columns, one line per item, where to link. If I can't picture the output, the agent can't either.
- Constraints. What to exclude, what to prefer, how to break ties. This is where house rules live: reuse over new abstractions, no new dependencies, match the existing test style.
- Edge cases. What to do when the input is empty, access fails, or the data is ambiguous. Deciding these up front is the difference between a result I can trust and one I have to re-check.
ROLE: You are a backend engineer working in this repository. You follow
its conventions and reuse its utilities.
TASK: Add a CSV export to the invoice list endpoint. Read the existing
list handler and the report export helpers first. Reuse the export helper
if one exists. Then implement, write tests, and run them.
OUTPUT: A short summary of the changes, the list of files touched, the
test results, and a three-step manual check.
CONSTRAINTS: No new dependencies. Match the existing serializer pattern.
Do not change the JSON response. Keep the diff to this feature.
EDGE CASES: Empty result sets return a header row only. Dates are
exported in the account's timezone. If the requesting user lacks the
export permission, return the existing 403 path; do not add a new one.
The working session
The loop runs inside a working session, and the session has a shape. The goal is one hour: plan, execute, test, deliver, then say "good job" and move to the next task. It won't always land inside the hour, and that's fine. The hour is a target, not a stop. The point is that the scope was chosen so it could.
One behavior, one pull request
A piece of work fits a session when it changes one behavior a user or a system would notice, lands in a single pull request, and carries its own tests. That's the whole sizing test. If it takes two sentences to describe the change, it's two slices. If the plan keeps growing while I refine it, it's more than one.
Slice bigger work
Most tickets are bigger than an hour. I slice them before starting and do the slices in sequence, each one its own session and its own pull request. A ticket for a new report might become three slices: the query and its data shape with tests, then the endpoint that serves it, then the screen that shows it. Each slice ships on its own. Each one starts with a fresh session and the context it needs, not the leftovers of the last one.
Slicing is my call. The agent can propose a split, but I decide where the seams are, because the seams have to make sense to the people who review, deploy, and maintain the code.
Run the hour
- Start with the deliverable named. One sentence for what will be true when the session ends. The plan is checked against it, and it's what done means.
- Load context and plan first. It feels slow. It's the fastest part of the hour, because it removes the rework.
- Execute with the plan open. The diff should look like the plan predicted. Anything else is a redirect.
- Test and review before the session ends. Tests run, the diff is read, the local review findings are disposed of, the pull request is opened.
- Say good job. Close the session. The next task gets a fresh one.
When it runs over
Finish it. A slice that's half done is the worst place to stop: nothing is shippable, and the context that made the work fast is gone by tomorrow. Take the extra time, land the pull request, and treat the overrun as information. Whatever made this slice bigger than an hour is what to slice differently next time.
Rules inside the session
- One task per session. Start fresh for the next one. Stale context is noise.
- Can't explain it, can't accept it. This applies to a line of code and to a plan.
- Match the depth of review to the blast radius. A log message and a migration don't get the same scrutiny.
- Ask the agent to argue against its own plan on anything non-trivial. It's good at finding the holes when asked.
- I am the author of record. The agent assisted. The name on the commit is mine.
Why it works
I hold one feature in my head at a time instead of five. The plan enforces alignment with the existing system before code exists. The diff review enforces house style after. Both checkpoints catch drift before anyone else does.
The plans accumulate. They become the record of how features were designed and why, useful for review, for onboarding, and for the next similar feature. Every document, decision, and resolved ticket I add to the agent's reachable context makes the next plan better.
And the speed is real. One focused session can ship a feature that would otherwise take days of context switching. The gain doesn't come from letting go of the work. It comes from taking hold of the right part of it.