A year ago, a lot of engineering discussion around AI was still about the editor.
How good is the autocomplete? Can I select code and send it to the model? Does the assistant understand the files I have open? How quickly can I move a suggestion back into the codebase?
Those questions already feel strangely old.
Once an agent can work directly against a repository, run commands, inspect output, change several files, open a browser, read logs, and repeat the cycle, the editor stops being the center of the workflow. The repository becomes the interface.
That changes the problem.
The hard part is no longer getting a model to produce code. Models are already quite good at that. The hard part is giving them a project in which they can work without requiring a human to explain the local religion every twenty minutes.
Most teams are attacking this from the wrong side. They keep trying to teach the agent more about the codebase.
I think we are going to spend much more time changing the codebase so there is less to teach.
Your repository is now an API
Take something painfully boring: starting the application.
In plenty of mature projects, the real answer to “how do I run this?” lives partly in a README, partly in somebody’s shell history, partly in a CI file, and partly in the memory of the engineer who has been there for six years.
Humans tolerate this surprisingly well.
An agent does not.
Give every project the same small set of obvious commands: setup, start, test, check, build. Put the ugly details behind them. The specific runner hardly matters. What matters is that there is one boring, executable answer to every common operation.
This was good engineering before coding agents existed. Agentic development makes the cost of ignoring it much more visible.
A project that needs interpretation at every step cannot support much autonomy. Every ambiguity becomes another point where the agent guesses, stops, or asks you to intervene.
That makes boring interfaces unusually valuable.
Stop treating instructions as documentation
Every agent now has some version of a repository instruction file. Teams immediately make the same mistake they made with internal wikis: they put everything in it.
Architecture notes. Coding conventions. Deployment instructions. Product background. Testing philosophy. Historical exceptions. A small autobiography of the engineering department.
Then they wonder why the agent ignores half of it.
The instruction file should contain things the agent needs almost every time: the commands, important paths, hard constraints, project-specific conventions, versions that are easy to guess incorrectly.
The rest should live closer to where it matters.
This distinction becomes important because an agent does not “know the repository” just because the information exists somewhere inside it. Files sitting on disk are not automatically context. A model still has to decide what to read.
The job is to make that decision easy.
Bigger context windows do not fix bad context
For a while I assumed the end state was obvious: context windows would get large enough to swallow the whole repository, and the problem would disappear.
That turns out to be the wrong mental model.
The problem is not fitting the information in. The problem is finding the few pieces that matter for the current decision.
Give a model a large amount of unrelated material and you have not made it smarter. You have given it more places to pay attention to the wrong thing.
Software projects already have this problem with humans. Nobody onboards a new engineer by printing every file in the repository and dropping the stack on their desk.
Agents need routes through the codebase:
“If you touch billing, read this.”;
“This file defines our domain terminology.”;
“These decisions explain why this part looks strange.”;
“This command verifies the browser behavior.”;
Good context is selective.
The teams that get this right will care less about the headline size of a context window than about how cheaply an agent can find the correct five files.
Pin the things humans infer automatically
A model can inspect package files. It can search imports. It can usually work out which weird framework you use.
It will still regularly choose a version that was standard in its training rather than the one you installed last month.
This sounds trivial until you spend weeks correcting the same class of mistake.
Humans read a repository with a huge amount of implicit context. We notice versions, naming patterns, directory shapes, recent commits, deprecations. We know that a library changed its API six months ago because we suffered through the migration.
The model itself may have a much stronger statistical memory of the previous version, simply because it was trained on it.
Write down the few details where guessing is expensive.
The point is not to describe everything. It is to remove predictable failure modes.
Do not let the agent define “done”
I used to think the important threshold was simple: can the agent verify its own work?
Can it run type checks, linting, unit tests, integration tests, start the application, use the affected screen, inspect logs, query a read-only database, fix a failure, and repeat?
That is necessary. I no longer think it is enough.
There is a slightly ridiculous arrangement hiding inside the usual agent workflow.
The same agent writes the implementation. It often writes the tests. It decides what is worth checking. Then it runs those checks, interprets the results, and declares the task finished.
That is a very convenient definition of accountability.
Humans are not allowed to operate like this in serious systems. The person making a claim usually does not get complete control over the evidence used to certify the claim. We separate implementation from acceptance because people are extremely good at proving what they already believe.
Agents have the same problem, with an extra talent: they are very good at producing a plausible narrative of completion.
So I have started moving the definition of “done” outside the implementation loop.
Before coding begins, the task gets concrete acceptance criteria. Prefer things you can observe from outside the implementation.
A user can perform this action;
This state appears after that action;
This record changes in the database;
This old behavior remains unchanged;
This failure produces this visible result;
Then, after the implementation claims to be finished, another pass tries to falsify that claim.
Actually use the feature;
Try odd states;
Inspect what was written to storage;
Compare the resulting behavior with the original request, not with the implementation plan;
Try the thing the implementation quietly assumes nobody will try;
If the evidence is ambiguous, the work is not done. It goes back into the loop.
There is an obvious objection: the verifier may also be an agent. Yes. This does not magically produce independent truth. Two model calls are not a formal proof.
But it still changes the structure of the problem – the implementer is no longer allowed to choose the exam, write the answers, grade the paper, and announce the result.
That makes completion harder to bullshit.
Ask the agent where your project is bad
Most people use coding agents in one direction.
“Build this”;
“Fix that”;
“Add a test”;
There is another use that becomes more valuable as the models improve: ask the agent to explain where it struggles.
How do you understand this repository?
Which commands do you use to verify changes?
Which parts are ambiguous?
Where do you have to infer conventions?
What information do you repeatedly search for?
Which patterns conflict with what you expect?
This is much closer to interviewing a new engineer after their first month than to asking for code.
The answers expose friction humans have learned to ignore.
That creates a useful engineering loop: watch where the agent fails, identify whether the failure is systematic, change the project, try again.
Do that for long enough and something strange happens. You stop optimizing prompts. You start refactoring the environment.
Sometimes the boring pattern is better because the model already knows it
Engineers have spent decades building local abstractions.
Custom testing DSLs. Clever wrappers. Internal frameworks. Slightly nicer versions of standard tools.
Some of those are genuinely better for humans.
They may still be worse for an agent.
This is uncomfortable because it introduces a property we never had to care about very much: how familiar is this pattern to the model?
Suppose your internal test framework is elegant, compact, and used by twelve engineers. A standard framework is uglier but appears in millions of public examples and has years of fixes, discussions, and documentation in the model’s training data.
You can write ten pages explaining your framework.
Or you can ask whether the difference is worth maintaining.
I have already seen projects improve after moving toward more conventional patterns. The model needs fewer instructions. It makes fewer structural mistakes. Verification gets easier.
This does not mean “always use the popular thing.”
It means model legibility is now a real engineering cost.
We used to ask whether an abstraction was easy for another engineer to understand, but now there is another reader.
Skills should describe workflows, not basic discipline
Reusable agent workflows are spreading quickly. Code review, performance analysis, debugging, domain analysis, planning, research.
They are useful. They are also easy to misuse.
If every feature should have tests, “remember to write tests” probably should not live inside a special workflow that the agent may or may not invoke.
That is a project rule.
A performance investigation is different. So is a security audit. So is an adversarial acceptance pass. These are explicit jobs with a beginning, a sequence of checks, and an end.
That is where reusable workflows make sense.
The distinction matters because agents do not reliably compose a pile of independent instructions into the perfect sequence every time.
If something must always happen, make it unavoidable.
If something represents a specific procedure, package the procedure.
Your codebase needs a vocabulary
This has been one of the more surprising improvements in my own work.
Before designing a feature, I increasingly want the agent to understand the domain first.
Not the database schema.
The domain.
What does “booking account” mean here? Is it the same thing in the frontend and the backend? Is “invoice” still the current term, or is it legacy vocabulary? Are two entities actually separate concepts that happened to share a table years ago?
Humans carry these answers around as tribal knowledge. The code carries fragments of them. Product copy carries more. Old migrations carry ghosts.
A good domain-modeling session forces the contradictions out.
The agent reads the code and existing docs, then starts asking annoying questions.
“Here you call this X. In another part of the system you call it Y. Which one is correct?”;
“This object appears to contain two different concepts. Is that intentional?”;
“This relationship exists in storage but not in the product language. Which version should future features use?”;
These sessions can take a while.
That is exactly why they are useful.
The output becomes a small ontology for the project: entities, terms, boundaries, relationships, legacy concepts.
Once that exists, future feature work starts from a much better position.
Save the rejected decisions
Most teams document what they chose badly enough.
Almost nobody documents what they considered and rejected.
That was already wasteful with humans. With agents it gets ridiculous.
A model can rediscover the same attractive bad idea every few months because, from its perspective, nothing in the repository says the argument already happened.
Record the context, the decision, the alternatives, and the consequences.
Then the agent has access to the reasoning, not just the resulting code.
This turns feature work into something more interesting than generating implementation plans. A design session can update the domain model, add a decision record, capture rejected options, and leave better context for the next session.
The project learns.
Not magically. Someone still has to make the decisions.
The important part is that the cost of preserving those decisions has dropped enough that we can finally stop throwing them away.
The architecture starts changing too
Once agents write a meaningful share of the code, another question appears:
Which code do I actually need to understand?
That sounds dangerous. In some parts of the system, it is.
There should be a core you protect aggressively: important invariants, critical data flows, security boundaries, interfaces whose failure spreads everywhere.
The interesting move is what happens around that core.
Take a self-contained UI component with several hundred lines of irritating state management. If it has one narrow interface to the rest of the application, good tests, and behavior you can verify from outside, do you care how elegant every internal function is?
I care much less than I used to.
If the component breaks, the agent can repair it. If it becomes ugly, it can rewrite it. If the interface remains stable and an independent acceptance loop can establish that the behavior is still correct, the internal code becomes cheap.
That suggests a different architectural pressure.
Make the protected core small. Make boundaries explicit. Make peripheral pieces easy to replace. Minimize the surface through which generated code can damage the rest of the system.
We spent years designing software around human comprehension and team boundaries. Now we are also designing for safe disposability.
That may turn out to be one of the bigger changes agentic development brings.
The engineer moves one level up
The engineer does not disappear from this process. The useful work just shifts.
Writing the implementation yourself is the old default. Letting an agent write it while you review every generated line is better. Designing a narrow interface and observable acceptance criteria so you rarely need to care how the implementation works is better still.
Telling the agent to “add tests” is weak. Giving it a project where it can run the full test suite and fix its own failures is good. Defining success before implementation, then sending a separate verifier to attack the result, is better.
Explaining the same strange internal convention in every session is bad. Writing it once in the project instructions is better. Removing the convention, when there is no strong reason to keep it, is best.
The same pattern shows up everywhere.
Do less work inside the implementation loop. Spend more time shaping the environment around it.
The experienced engineer becomes more valuable at the points where judgment is hardest to automate: deciding what must remain true, choosing boundaries, defining the domain, deciding what evidence counts, and noticing when the system keeps making the same mistake for structural reasons.
Code still matters.
The more interesting question is increasingly what has to be true around the code before you are willing to stop looking at it.
TLDR; Make trust unnecessary
There is a temptation to describe the direction of travel as “learning to trust the agents.”
I think that gets it almost exactly backwards.
I do not want to trust a coding agent because it sounds confident. I do not even want to trust it because it ran the tests that it helped write.
I want the project structured so trust matters less.
That means obvious commands instead of community knowledge. Selective context instead of dumping the repository into a giant window. Conventional patterns where clever local abstractions buy us very little. Explicit domain language. Decisions that survive the meeting in which they were made. Small interfaces around code we are happy to replace.
Most of all, it means separating producing the answer from deciding whether the answer is acceptable.
That feels like the important line.
If I had to squeeze this whole episode into one idea, it would be this: stop asking how to make the agent better at your project, and start asking how to make your project harder for an agent to misunderstand – and harder for it to falsely declare finished.
That is a much less fancier job than trying the newest model every week.
It is also starting to look like the work that matters.
We spent decades making software understandable to the fellow human who had to change it. Now we have another fellow: absurdly fast, broadly knowledgeable, strangely forgetful about local facts, and extremely capable of giving you a convincing explanation for something that is still wrong.
So, just, prepare for that guy.
I suspect we are still very early in figuring out what software built this way should look like. That is what makes this period interesting. Meanwhile, as usual, I’ll see you in the next one.



