The AI Can Clean Up 60,000 Feature Flags. That Doesn't Make 60,000 Feature Flags Good Engineering.
There is a particular kind of technology story that makes me incandescent.
Not because the technology is bad. Often it is genuinely clever.
It is because we take a problem created by years of accumulated engineering practice, build an impressive system to clean up the consequences, and then tell the story as though the cleanup itself is evidence of engineering excellence.
A recent InfoQ story about DoorDash describes how DoorDash has built a multi-agent LLM system to clean up stale feature flags across its software estate.
The numbers are impressive. DoorDash reportedly manages more than 60,000 feature flags across roughly 623 repositories, creates about 2,300 new flags each month, and has identified more than 1,000 stale flags. Its agentic system can investigate a flag, understand how it is wired through the codebase, make the changes, run validation and produce a pull request for engineering review.
That is technically interesting.
The agents are doing real work. The dependency-injection patterns involved mean that removing a flag is not necessarily a simple Boolean deletion. DoorDash reports that a single flag can involve changes across five to twenty files, including tests, with relationships between the flag and application logic that are sometimes semantic rather than syntactic.
But there is another way of looking at the story.
Why did those temporary things become a population of 60,000?
And why are we so impressed that an AI can clean them up?
A feature flag is not configuration
At AppGenie, we have a very simple rule.
A runtime switch is one of two things.
It is either a feature toggle, which is a migration path, or it is an operational control, which is an executable path responding to an external factor.
The distinction matters.
A feature toggle exists because the system is temporarily between two behaviours. It therefore has a lifetime.
Its eventual state is not "false".
It is not "inactive".
It is not "we haven't touched it for a while".
Its only permitted end state is removal.
An operational control is different. It exists because the system genuinely needs to respond at runtime to something external: dependency health, entitlement, licensing, regulatory state or another explicitly identified condition.
That control can be permanent.
The test is simple.
Remove the switch and commit the system to one branch.
If the system can no longer respond to an external factor, it was an operational control.
If the only consequence is that the system commits to the new behaviour, it was a feature toggle.
We don't leave that distinction to interpretation after the fact.
It is written down in the AppGenie Engineering Coding Standard, ENG-CTRL-01, version 4.1.
Section 5.13 is the control.
And it is deliberately uncompromising.
A feature toggle cannot be environment-injected.
It cannot have different values in different environments.
Its default must be the new behaviour.
It must be introduced with a removal date and linked work item.
It has exactly one permitted end state: removal.
Removing the toggle while leaving the superseded code path behind is non-compliant.
And past its declared removal date, the build must fail.
This is not a recommendation.
It is not a retrospective cleanup process.
It is a control.
Both constructs are declared by a marker at the declaration site. The marker text is identical in every language; the comment syntax is whatever the language uses. That is what makes this machine-checkable rather than reviewable.
feature-toggle: <Name> removal:<YYYY-MM-DD> ref:<WORK-ITEM>
operational-control: <Name> factor:<external factor> owner:<role> ref:<REGISTER-ENTRY>
The declaration sits with the code it governs, not in a separate register. A register maintained alongside the code drifts from it. A register generated from the code cannot.
And the removal date is not an honour system. It is enforced by validate-feature-toggles.py in our estate DevSecOps repository, wired blocking into the build templates. The standard puts it more bluntly than I would: a control that only a human checks is the control that already failed here.
The interesting part of the DoorDash story is not the AI
The AI system DoorDash has built is interesting because it is dealing with a problem that is genuinely difficult to automate.
The less comfortable question is why the organisation has accumulated a problem at that scale.
This is not an argument that DoorDash developers are careless.
I have no basis for making that claim, and I certainly would not pretend that I could walk into a 623-repository software estate and immediately understand it better than the people who built it.
I couldn't.
That is actually the point.
I am not smarter than every developer at DoorDash.
I'm not smarter than every developer at any large engineering organisation.
I've simply been bitten by a lot of things.
I've been developing software for roughly four decades.
Four decades is long enough to see the same failure modes come around wearing different clothes.
Configuration that became permanent.
Temporary migration code that became architecture.
Environment variables that silently changed application behaviour.
Database switches that nobody could explain.
Dependencies that nobody knew were there.
Code paths that were "dead" until somebody discovered that production depended on them.
Documentation that described what the system was supposed to do rather than what it actually did.
Controls that existed on paper but had never been exercised.
And, eventually, somebody asking the question that should have been asked at the beginning:
What exactly are we running?
That accumulated experience is what goes into the AppGenie controls.
Not genius.
Experience.
Don't believe me. I learnt this lesson from Doug.
If you think I'm being unnecessarily paranoid about controls, don't take my word for it.
Go and read Doug Seven's Knightmare: A DevOps Cautionary Tale.
Doug's account of the Knight Capital incident is one of those engineering stories that stays with you because the individual decisions involved are much less interesting than the system they eventually produced.
The lesson isn't that developers are stupid.
It is almost the opposite.
People make reasonable decisions in the context they can see.
Systems retain decisions long after the context that justified them has disappeared.
That is why controls matter.
And if you want to know exactly what AppGenie thinks should happen with feature toggles, don't take my word for that either.
Go look for ENG-CTRL-01, Section 5.13.
It is in the AppGenie Compliance MCP's controlled standards.
The lesson shouldn't live in this blog post.
It shouldn't live in my head.
It shouldn't depend on whether the developer implementing the next migration happens to have been bitten by the same problem before.
The lesson belongs in the control system.
This is why we built the Compliance MCP
The AppGenie Compliance MCP exists because putting a standards document in a folder and asking an LLM to read it is not enough.
An AI assistant can retrieve a document.
It can summarise it.
It can produce an extremely convincing answer.
None of that means the resulting work is controlled.
The AppGenie Compliance MCP puts organisational rules and controls into the environment in which the AI is making decisions.
The point isn't to make the model smarter.
The model already has plenty of general knowledge.
The point is to give it the specific accumulated experience of the organisation, expressed as controlled, applicable rules.
That is a fundamentally different proposition from asking an LLM:
The question we actually want the system to answer is:
That is why the Compliance MCP exists.
This isn't just a feature-flag rule
This is where the broader AppGenie control model matters.
Section 5.13 doesn't sit in isolation.
The same ENG-CTRL-01 standard defines explicit non-compliance conditions, including a runtime switch introduced without being declared as a feature toggle or operational control, a feature toggle past its removal date, a feature toggle removed while its superseded code remains, and an operational control that is unregistered, unowned or has never been exercised.
It also defines what counts as evidence.
Source-control history.
Linked work.
Pull-request review.
Test results.
Static analysis.
Security scanning.
Traceability.
And it explicitly says:
Developer statements are not evidence.
That sentence exists for a reason.
The standard then defines enforcement points across development, local validation, code review, static analysis, testing, CI/CD, release readiness and audit review.
Failure against the standard blocks progression unless an approved exception exists.
This is what turns "good engineering practice" into an actual control.
This is the same problem as the SBOM
This is why the feature-flag discussion connects directly to something else we have written about at AppGenie:
Can You List What Is In Your Software?
That article asks a deceptively simple question:
What is actually in this software?
An SBOM answers part of that question.
But software has another inventory that we tend to ignore.
Behaviour.
What behaviours can this system execute?
What decisions can change at runtime?
Which decisions are permanent?
Which are temporary?
Which are environment-dependent?
Which are externally controlled?
Why does each one exist?
Who owns it?
When does it expire?
What evidence proves that it has been exercised?
A dependency inventory without a behavioural inventory is incomplete.
You can know every library in your application and still have no idea which of several materially different behaviours production will execute.
That is not merely a documentation problem.
It is a control problem.
The dangerous phrase is "we can clean it up later"
This is one of the oldest traps in software engineering.
We'll remove that later.
We'll document that later.
We'll rationalise the configuration later.
We'll consolidate the environments later.
We'll update the dependency later.
We'll put the evidence together before the audit.
Sometimes "later" works.
Usually, later is when the person who made the decision is no longer thinking about it.
The system has moved on.
The people have moved on.
The repository has changed.
The operational environment has changed.
And the temporary thing has become part of the system.
This is why AppGenie controls tend to be deliberately uncomfortable.
They are not designed to make the developer feel good.
They are designed to make the failure mode difficult.
Compliance should encode experience, not paperwork
This is also why I don't think compliance should be treated as a collection of PDFs that developers consult when someone from governance asks for evidence.
That model puts compliance downstream of engineering.
We are interested in the opposite.
Put the control where the decision is made.
If a developer introduces a migration mechanism, the control should apply there.
If an AI assistant is writing the implementation, the control should apply there.
If an agent is preparing a release, the control should apply there.
If a system claims that something is an operational control rather than a feature toggle, there should be a rule that requires the distinction to be made and evidenced.
The Compliance MCP exists to make those controls available to the systems and agents making those decisions.
The result is not "AI compliance".
It is something much more practical:
engineering experience that a machine can apply.
We are not claiming to be smarter
This is probably the most important thing I can say about what we are building at AppGenie.
I don't think I am smarter than the developers building software at DoorDash.
I don't think the AppGenie team is smarter than the engineering teams at Google, Microsoft, Amazon, DoorDash or any of the other organisations operating software at enormous scale.
What we have is something much less glamorous.
Experience.
Four decades of watching reasonable decisions accumulate into unreasonable systems.
Four decades of seeing the same problem emerge after the original context has disappeared.
Four decades of learning that "temporary" is one of the most dangerous words in software.
Four decades of learning that the best control is usually the one you apply at the point where the decision is made, rather than the one you apply after the consequences have accumulated.
And four decades of seeing what happens when somebody doesn't have that control.
Doug's Knightmare is one of those lessons.
There are many others.
You don't need to be smarter than the people who made the original mistake.
You just need to have learned from it.
That accumulated experience becomes standards.
The standards become controls.
The controls become machine-readable.
The machine-readable controls become available to the AI through the Compliance MCP.
And now the AI gets something much more valuable than another prompt.
It gets the benefit of the scars.
The goal isn't to make AI clean up our mess
AI will absolutely make it cheaper to clean up technical debt.
That is valuable.
The DoorDash work demonstrates that clearly.
But that should not be confused with preventing the debt.
There is a difference between:
and:
The first is an impressive capability.
The second is a control.
And that distinction is increasingly important as AI starts writing more of the software itself.
Because once an AI can create code at enormous speed, we cannot rely on somebody eventually finding everything it created.
We need to put the accumulated experience of engineering into the environment in which those decisions are made.
That is what the AppGenie Compliance MCP is for.
Not to make the AI cleverer.
Not to replace developers.
Not to pretend that a compliance catalogue makes software safe.
To make decades of lessons learned available at the moment a decision is being made.
Because the most valuable engineering experience is often not knowing how to build something.
It is knowing what you will regret building if nobody stops you now.