sysid blog

The Decisions Nobody Made

Code Is Cheap. Being Wrong Isn’t.

There are four sentences I increasingly hear around software development:

“The tests passed.”

“Copilot suggested it.”

“It looked fine in review.”

“The agent handled the implementation.”

None of them sounds negligent. In fact, they sound perfectly reasonable. But they are symptoms of a deeper problem.

Coding agents have become extremely good at producing software. They can implement features, perform migrations, generate tests, investigate unfamiliar APIs, fix compiler errors, and grind through the sort of repetitive work that previously consumed entire afternoons and significant quantities of coffee.

We should definitely use them for that.

The problem starts when delegation of implementation quietly becomes delegation of judgment.

Those are not the same thing, of course.

Responsibility did not move

Suppose an agent generates a concurrency strategy that works up until staging, but collapses under production load. Who made the decision to use that strategy?

“The agent chose it” is not an answer available to a professional engineer.

The agent has no customer relationship to damage, no reputation inside the company, no pager, no employment contract and no career that suffers from a bad decision. It will not explain the outage to the CEO. It will certainly not spend Sunday afternoon rebuilding the corrupted data.

This is not a limitation of model intelligence. Responsibility is not an intelligence benchmark. This implies that better models will not fix it.

Responsibility requires someone who can bear consequences, make a decision and can refuse to make one.

An agent can recommend PostgreSQL over DynamoDB. It can produce an excellent comparison of both. It can create the Terraform configuration before you finish reading that comparison.

But somebody still has to say: for this system, with these consistency requirements, this operating model, these engineers and this expected lifetime, we are choosing PostgreSQL.

That person owns what the choice excludes as much as what it enables.

The important distinction is therefore between bearing responsibility and exercising responsibility.

Engineering teams continue to bear responsibility for their systems. Customers and management have certainly not agreed otherwise. But it is becoming increasingly easy to stop exercising that responsibility: just accept the generated design, see the green tests, merge the pull request and move on.

Responsibility you bear but no longer exercise is not delegation.

It is exposure.

First, say what kind of software you are talking about

A surprising amount of discussion about AI-assisted development is useless because people are talking about different kind of software.

A developer vibe-coding a side project for twelve users and a professional team maintaining a ten-year-old business-critical enterprise platform do not operate under any comparable constraints.

For a disposable prototype, telling the agent to implement the entire application may be completely rational. If the architecture turns out to be terrible, delete the repository. This is actually an underrated scaling strategy.

The calculus changes when software controls revenue, customer data, contractual obligations or operational processes that must continue working for another decade.

The relevant dimensions are not whether the code was “vibe-coded” or “professionally coded.” They are blast radius, reversibility, expected lifetime and who pays when it fails.

A prototype can tolerate structural mistakes, an internal tool already deserves some care.

A customer-facing product deserves considerably more diligence.

And a financial, regulated or safety-critical system deserves enough caution that “the agent seemed confident” should definitely not appear in the incident report.

If being wrong costs… Your Responsibility
your own afternoon almost none. Let it run
your team a week a README that states the shape, and tests you would actually trust
a customer money or trust the data model, the invariants and the relevant tests, decided by you and written down
somebody’s safety all of the above, plus independent verification, plus an explicit boundary the agent does not cross

Agents are very good at local work

Coding agents have a structural advantage at implementation tasks: software provides exceptionally rich feedback loops and agents are trained with all public code written so far.

The compiler tells them when syntax is wrong. Tests tell them when behaviour violates known expectations. Linters and static analysis complain. Benchmarks provide numbers. CI either turns green or red.

This makes a huge amount of engineering work delegable.

If I have already decided the API contract, data model, invariants and acceptance criteria, I am quite happy for an agent to write the implementation.

It can create boilerplate.

It can perform mechanical refactoring.

It can migrate an API.

It can generate test cases.

It can investigate libraries and present alternatives.

It can fix straightforward defects.

It can even review another agent’s work.

Refusing to delegate such work is not craftsmanship, it is rather negligence and wasting resources.

The interesting question is where the feedback loop stops being sufficient.

Hard decisions do not have fast oracles

Consider a data model.

There is usually no test that tells you, that the chosen representation will make every future feature painful.

The compiler cannot tell you that a boundary belongs in another subsystem.

A unit test cannot tell you that a queue should not exist.

Your integration suite cannot reliably determine whether the system should use threads, asynchronous I/O, processes, event-driven messaging or, occasionally, the revolutionary approach of doing one thing after another.

These decisions shape the future option space of the product.

None of these decisions results in red flags as long as your tests pass. So the agent optimizes what it can see and finds a good local solution, because local is where the obvious signal is.

Agents tend to optimize what they can see easily. In a large system that often means finding a good local solution. However, professional engineering frequently requires rejecting the locally attractive solution because of something outside that local context: a global project invariant, an operational constraint, an organisational boundary, an obscure customer requirement or a decision made six years ago for a reason nobody thought worth adding to the README.

That is where human responsibility matters.

Not because humans can better keep an entire enterprise system in their heads. We cannot!

The difference is that a competent and responsible engineer knows there are things he does not know and is accountable for discovering which of those things matter before committing the project to a decision.

Tests are evidence, not responsibility

One particularly tempting failure mode is replacing design with verification.

Agents are excellent at producing tests and assertions. That is useful. But more assertions are not automatically better software.

Sometimes an impossible state should just be checked. Often a better solution is, to make it impossible altogether.

If a domain object can exist in seventeen combinations but only four combinations are valid, adding thirteen runtime checks is one solution.

Designing a type or abstraction that represents only the four valid combinations may be the better one.

This distinction matters because generated code often looks surprisingly defensive. Every function checks its input. Every exception is caught. Every failure has a fallback.

And yet the overall system may have become harder to reason about.

Karpathy described models as being “mortally terrified of exceptions.” The phrase describes a real problem: defensive code is becoming a substitute for choosing strong invariants.

A responsible engineer asks a different question: What states should this system be capable of representing at all?

That is a critical architectural question, not a test-generation question.

Likewise, the human responsibility around testing is increasingly not writing the tests.

It is deciding which tests constitute relevant evidence.

The Bun problem

Large public agentic successes are easy to misread.

A mechanical transformation is extraordinarily well suited to agents when the target architecture is already known and an unusually strong test suite supplies rapid external feedback. Bun’s Zig-to-Rust port had both: a test suite written in a language independent of the implementation, and an explicit decision to make the Rust look like a transliteration of the Zig so that nothing had to be redesigned.

Every hard design decision in that project had already been made, by humans.

That is not evidence that agents can design systems. It is evidence that agents are remarkable effective once the difficulty design decisions have been made.

However, this does not imply a twenty-year-old enterprise application can be treated the same way.

Legacy systems contain enormous amounts of implicit knowledge.

Some lives in tests.

Some lives in documentation.

Much of it lives in production configuration, customer workarounds, historical database contents, operations procedures, forgotten integrations and sentences beginning with, “Yes, that looks strange, but in 2019 …”

The real effective specification is often only the behavior customers have learned to depend on.

This is why “rewrite it with agents” is much less convincing for mature systems than it first appears. The challenge is not generating equivalent code. The challenge is assessing and deciding what “equivalent” means.

What to hand over, and what to keep

This is not an argument for keeping agents on a short leash. Quite the opposite. Let the agent loose where failure is cheap

If humans remain occupied with boilerplate, straightforward refactoring and mechanical implementation, we are wasting resources and sacrificing speed.

The skill is learning where to let the agent run aggressively and where to stay firmly in control.

Type of work Is there a target the agent can hit? Who
Implement against an agreed contract Yes, written down Agent
Port or migrate against a full test suite Yes, total Agent
Fix a reproducible defect Yes, the reproduction Agent
Hunt vulnerabilities in existing code Yes, verifiable search Agent
Write tests for behaviour you have defined Yes Agent
Boilerplate, glue, mechanical refactoring Yes, the conventions Agent
Decide which tests would prove it works No. This is choosing You
Choose the data model and its invariants No You
Choose the API, and what it forecloses No You
Decide what “fast enough” and “reliable enough” mean No. It is a risk you accept You
Decide the feature should exist at all No You

Everything in that second category is either choosing a target or noticing that the target was wrong.

Let the agent generate the adapter. But stay on top of the domain model. Let it migrate three hundred repetitive API calls. But stay on top of the transaction boundary. Let it propose five scaling strategies. But decide whether the system needs to scale that way at all. Let it write fifty tests. You decide whether those tests prove anything important.

The line between what an agent can do and the human must keep doing moves, and it has been moving towards the agent the whole time. Expect more to cross. What should not cross is choosing and noticing, because those need stake, and stake is not a capability.

The less reversible a decision is, the more explicitly human its ownership should be. And that ownership must have a real person’s name attached to it.

The developer job is moving up the responsibility chain

There is an uncomfortable consequence for individual contributors.

For a long time, a substantial part of the developer’s role was translating product requirements into implementation.

The product owner explained what was needed. The developer turned it into software.

Coding agents dramatically reduce the cost of that translation.

Once a product owner can describe a sufficiently clear feature, an agent can increasingly perform much of the implementation directly.

So “I take tickets and turn them into code” is becoming a poor professional moat.

The answer cannot be to type faster than the agent. It is feature responsibility.

A strong engineer increasingly needs to understand four things.

First, the business process and customer value. Why should this capability exist? Which customer problem does it solve? What behavior would technically satisfy the ticket while completely missing the point?

Second, the product architecture. Where should the change live? Which boundaries should remain intact? Which infrastructure is appropriate? Does the problem actually require concurrency, messaging or another distributed component?

Third, operations. How will the feature behave in production? What needs to be observable? What is the expected service level? How does it degrade? What happens during rollback?

Fourth, verification. What evidence would convince us that the requirement has actually been met? Which tests matter, rather than merely which tests are easy to generate?

Agents can assist with all four. They can research customer workflows, compare architectures, draft SLOs, generate threat models and write test suites.

But assistance does not transfer the decision.

The human in charge must be able to explain the choice without saying, “That is what the model suggested.”

The moment that sentence becomes your final justification, you have stopped being the engineer and become an expendable parrot.

Smaller teams, larger ownership

There is also an optimistic version of this future.

If implementation becomes dramatically cheaper, teams may become smaller while the scope of individual responsibility becomes broader.

A product owner, a senior engineer and a developing engineer equipped with strong agents may accomplish what previously required a much larger delivery team.

But the smaller team cannot simply be a compressed version of the old one.

Its members need broader ownership.

They need to understand the feature from customer need through architecture, implementation and operation.

That is demanding, but it is also much closer to what software engineering should have been all along.

What remains ours

Coding agents changed the cost of producing code.

They did not change the cost of being wrong.

They did not change who is called when production fails.

They did not change who explains the decision to a customer.

They did not change who decides that a feature should not be built.

And they did not change who must understand enough of the system to make a consequential decision deliberately.

It used to be easy to think our primary artifact was the diff: the code we personally produced and reviewed.

Increasingly, the artifact we must own is the constraint set around that diff: the business intent, architecture, invariants, operational requirements and evidence by which correctness is judged.

Inside those constraints, an agent can do an enormous amount of the work.

This is the opposite of surrendering engineering to AI. It is using automation exactly where automation belongs.

Delegate hard. Decide deliberately.

And when the system makes a decision that matters, make sure there is still a human in the room who is prepared to say:

“I chose this, and I can explain why.”

Remember: Responsibility is not an intelligence benchmark!

#Ai #Development #Agents