Every AI rollout comes with a dashboard. PRs merged per week, time from ticket to prototype, model accuracy on the eval set. All of those numbers go up and to the right in the first quarter, and all of them are measuring the same thing: how fast you can produce something new.
None of them measure what happens to that something eighteen months later.
That's the gap. The demo version of a system is judged on how quickly it appeared. The production version is judged on whether it's still understandable, still stable, and still changeable after three teams, two reorgs, and a vendor price hike have happened to it. AI made the first part dramatically cheaper. It did nothing for the second part, and in some teams it made it worse.
Here are three tests I use instead of velocity. None of them are new. That's the point.
Test 1: can a new developer read it without an AI explaining it?
Illustrative scenario: a team generates most of a service with an assistant. It works. Six months later, a new hire is asked to change how refunds are calculated. They open the code and find four helpers named processData, handleResult, transformPayload and finalizeResponse, each one 200 lines, each one doing a slightly different slice of refund logic. Their first move is to paste it into a chat window and ask what it does.
That's the smell. If the only way to understand your codebase is to ask a model to summarize it, the code isn't documenting itself, and you've made comprehension a dependency on a tool.
What to check:
- Can someone find where a business rule lives by reading file and function names alone?
- Are the domain concepts (Refund, Invoice, Tenant) visible as types, or buried in dictionaries passed between generic helpers?
- Would a code review catch a wrong rule, or would the reviewer also need to ask the model?
# Hard to read without help
def handle_result(data, cfg, flag=False):
...
# Readable on its own
def calculate_partial_refund(order: Order, returned_items: list[LineItem]) -> Money:
...
AI-generated code isn't the problem. Unreviewed, unnamed, unstructured code is, and AI lets you produce it at a volume nobody can review.
Test 2: does it run without someone babysitting it?
A mature system is boring in production. A system that needs a person to restart a worker every Tuesday, re-run a stuck job, or manually clear a queue is not stable. It's being held up by a human who hasn't been written into the architecture diagram.
Illustrative scenario: an ingestion pipeline built quickly with lots of generated glue code. No idempotency, no dead-letter queue, retries implemented three different ways in three places. It mostly works. "Mostly" means one engineer spends a few hours a week nudging it, and when that engineer goes on holiday, the on-call rotation discovers the runbook is "ask Sam".
What to check:
- How many manual interventions did production need last month? Is anyone counting?
- Are failures handled by design (retries with limits, idempotent handlers, clear failure states), or by people?
- Could the system survive the departure of the person who knows its quirks?
Test 3: can you swap a core dependency without rewriting the business logic?
This one matters more now than it did three years ago, because the dependency most teams just added is the one most likely to change. Model providers change pricing, deprecate versions, and change behavior between releases. If your provider's SDK is imported in forty files and its response shape has leaked into your domain objects, switching is a rewrite.
// Business logic coupled to one vendor
const res = await openai.chat.completions.create({ ... });
order.summary = res.choices[0].message.content;
// Business logic depends on a capability, not a vendor
interface Summarizer {
summarize(text: string): Promise<string>;
}
order.summary = await summarizer.summarize(order.notes);
The same applies to your database, your queue, your payment provider, and your auth vendor. The question isn't whether you'll ever swap them. It's whether the decision is yours to make, or already made for you by how deeply they're wired in.
The reframe
Velocity tells you how fast you can create code. Maturity tells you how cheaply you can live with it. AI changed the price of the first one. The second one is still priced in discipline: clear naming, stable failure handling, and clean boundaries around things you don't control.
AI is a tool. A very good one. But the systems still standing in five years will be the ones that would have been well built without it.
What's the one signal you trust more than velocity when you judge whether a codebase is actually healthy?
Top comments (19)
I understand the wish to find a number for a developer's work, and I agree velocity is the wrong one. But the problem is older than AI. Every developer KPI I've seen broke the same way:
So AI didn't create the measurement problem. It made the wrong numbers grow faster.
I like that your three tests are questions, not numbers. Test 3 is actually a rephrasing of a principle every professional developer knows: dependency inversion from Clean Architecture, where business logic doesn't depend on frameworks, databases or vendors. To me that's the best part of your argument. The principles that made code last before AI are the same ones that make it last now. AI just makes it easier to skip them.
To answer your question, the signal I trust most is how long it takes a new person to make a safe change, and whether someone on the team can still explain why the code is the way it is.
That last point is probably the best answer to the question. How long does it take a new person to make a safe change, and can someone explain why the code works this way? I agree the measurement problem existed long before AI. AI just makes it much easier to produce impressive numbers while hiding the actual cost of maintaining what we build.
I think we should put more effort into automating testing and review, because that's where the hidden cost gets caught early. You said AI makes it easy to produce impressive numbers while hiding the maintenance cost. A test or a review check that runs on every change can't be impressed by a demo. I've started asking the agent to write more types of tests, not just unit ones. Golden screen tests, and e2e tests for the MCP server (when tested, it is connected as a client). Maybe that helps in your field too.
The "year two" framing is the right test. Almost any codebase looks healthy in the first month, and AI assistance mostly compresses that first month further. The bill arrives later, when someone has to change a feature nobody fully understood when it was generated.
In client builds I have found it helps to treat maintainability as an explicit acceptance criterion rather than a hope. If the definition of done for a piece of work includes tests for the critical path, a short note on why the main decisions were made, and a deploy that someone other than the author can run, the speed gains from AI stop being borrowed against the future.
The other thing that helps is keeping each unit of work small and bounded. A single, well defined problem with a clear outcome is much easier to review and hand over than a sprawling feature that grew one prompt at a time. Curious whether you track any metric for this, such as time to first change by a new contributor.
I like the idea of making maintainability part of the definition of done instead of something we hope for later. Small, bounded changes also make a huge difference when AI makes it so easy to generate more than anyone can realistically review. I don't track time to first change by a new contributor yet, but honestly, that's a metric worth trying.
Test 3 is the one I'd push on, because the interface fixes the compile-time half of a swap and not the half that bites. Summarizer.summarize() keeps the same signature across vendors, but output length, tone and what gets dropped all change, and nothing in the type system notices. So the signal I'd trust more than velocity is whether a dependency swap comes with something that can fail: a dozen saved inputs with properties the output must keep, run against both providers before the switch. Without that, a clean boundary mostly makes it easier to swap in a regression.
Fair point. An interface makes the swap easier mechanically, but it doesn't guarantee equivalent behavior. I like the idea of keeping representative inputs and checking what must be preserved across providers. I'd add that to the boundary itself, because otherwise we've only solved half the problem.
Đúng trọng điểm. Dashboard đẹp mắt (PRs/week, cycle time giảm, prototype nhanh) chỉ đo lường velocity, không đo sustainability. Year two mới bộc lộ chi phí thực: technical debt tích lũy từ code gen không review kỹ, test coverage giả vờ, architecture bị ép vào pattern LLM phù hợp demo chứ không phù hợp domain, và team mất khả năng debug khi black-box model thay đổi behavior.
Thấy nhiều team celebrate "shipped in 2 days" rồi month 6 vẫn đang chase flaky test, refactor spaghetti prompt chain, và explain cho stakeholder tại sao feature "done" lại break khi model update minor version. Engineering maturity là biết khi nào không dùng AI, và xây guardrail cho những chỗ dùng — không phải maximize metric dashboard (site: labagent .tech)
Exactly, especially the part about knowing when not to use AI. Shipping in two days means very little if the next six months go into debugging and patching it. The tool should help us make better engineering decisions, not become an excuse to skip them.
I don't think there's a hard signal for this, but rather a "soft" one. Ask the developers what they created and why it matters to the business. If they can clearly explain what they built, why it exists, and the value it provides, that's probably a pretty good sign.
I like this take. If a developer can explain what they built, why they made those choices, and how it helps the business, that tells you a lot more than a PR count. The real test is whether they can still explain it six months later when something breaks or the requirements change.
The manual fixes per week metric hits home. I've shipped enough small tools to know that "it works" and "I never have to touch it" are very different things. The second one takes more thought upfront.
Exactly. Getting something to work is often the easy part. Getting it to keep working without someone constantly checking on it is a completely different engineering problem. The hidden maintenance hours are what make the real difference.
Some comments may only be visible to logged-in visitors. Sign in to view all comments.