We've all been in that repo. The one where npm install prints 47 warnings, the README still references a v1 API that was renamed two years ago, three tests are skipped "for now," and the last meaningful commit on master was a merge from a branch nobody remembers. You clone it, run it, and it kind of works — until it doesn't.
We call this technical debt, but that framing is wrong. Debt implies you borrowed something on purpose and have a plan to pay it back. What most repos actually have is drift — the slow, invisible accumulation of small mismatches between what the code does, what the docs say, what the dependencies expect, and what the tests assert. Nobody chose it. Nobody owns it. It just happens, because maintenance is unpaid labor and shipping features is paid labor.
Here's the thing I've come to believe after maintaining enough projects: technical debt is not a coding problem. It's a maintenance problem. And that distinction matters, because it changes what tooling can do about it.
Why "just write better code" doesn't fix it
If tech debt were a coding problem, better engineers and better reviews would solve it. And sure, those help at the margins. But drift doesn't come from bad code — it comes from the gap between when a change is made and when the surrounding context catches up.
Consider a few examples I bet you recognize:
- Stale dependencies. A patch release of a transitive dep fixes a CVE. Nobody bumps it because nothing is broken yet. Six months later you're on a version three majors behind and the upgrade is now a project.
- Docs out of sync. You rename a function, update the call sites, and forget the README example. Or the architecture doc references a service that was deleted. The docs don't fail CI, so they rot silently.
- Tests that lie. A test gets skipped during a refactor "to fix later." Later never comes. Now your green build is hiding a real regression.
- Repo drift across services. Service A expects payload shape v2. Service B still sends v1. It works in dev because dev uses mocked data. Prod finds out at 2am.
None of these are bugs in the code. They're failures of synchronization — the ongoing work of keeping a repo coherent with itself and its environment. It's maintenance. And it's exactly the kind of work humans are bad at scheduling, because it never feels urgent.
The shape of a real solution
If the problem is maintenance, the fix isn't a smarter linter or a one-off cleanup sprint. It's a continuous maintenance loop — something that runs on a schedule, looks at the repo as a whole, and makes small, reviewable changes to keep it healthy.
A practical loop looks like this:
- Observe — read the repo state: dependency manifests, test output, docs, recent commits, open issues.
- Decide — figure out what's drifted and what's worth fixing right now (prioritize security > tests > deps > docs).
- Act — make the change: bump a version, update a doc example, un-skip and fix a test, open a PR.
- Verify — run the tests, the linter, the build. If it's red, fix it or revert.
- Repeat — on a schedule, not when someone remembers.
The key insight is that steps 1, 2, and 4 are mostly mechanical, and step 3 is increasingly within reach of AI coding agents. You don't need a human to notice that package-lock.json has a CVE advisory, or that the README's install command references a flag you removed. You need something that does something about it without waiting for a sprint planning meeting.
A pattern that actually works: agents as peers, not orchestrators
Most "AI for maintenance" setups I've seen fall into the same trap: a single big prompt that says "fix my repo." That works once, badly, and then you forget it exists. The repos that stay healthy use a different pattern — multiple focused agents that each own a slice of the maintenance surface, coordinating as peers.
Instead of one agent trying to understand everything, you have:
- A deps agent that watches for outdated/CVE'd packages and bumps them one at a time.
- A docs agent that diffs code against docs and rewrites the stale parts.
- A tests agent that finds skipped/failing tests and tries to fix them.
- A hygiene agent that looks for dead code, broken links, and config drift.
They don't report to a central brain. They share a message bus, see each other's work, and avoid stepping on the same files. If the deps agent is mid-PR on package.json, the docs agent waits before touching the install instructions. This is closer to how a real on-call rotation works than how a "AI orchestrator" works.
A concrete example
Here's what this looks like in practice with AutoMaintainer, a daemon that runs this exact loop using AI agents over a peer-to-peer message bus. You register a repo with a plain-English focus and start the daemon:
# Register a repo and tell the agents what "healthy" means for it
am repo add https://github.com/you/myproject \
--focus "keep docs in sync with code, fix failing tests, bump outdated deps"
# Start the maintenance daemon locally
am start --local
That's the whole setup. The daemon spawns an agent team per repo, each agent picks up its slice of the focus, and they start opening PRs. You review the PRs like you would any other. The agents handle the observe-decide-act-verify loop on a schedule; you stay in control of what merges.
The focus string is doing real work here. "Keep docs in sync" means something different for a library than for a microservice, and you want to be able to say "don't touch the public API" or "prioritize the test suite over dep bumps this week" without rewriting tooling. Plain English as config is underrated.
The source is on GitHub: https://github.com/javimosch/automaintainer — worth reading if you want to see how the peer coordination actually works under the hood.
## What this gets you, and what it doesn't
Let's be honest about the tradeoffs.
**What it gets you:** the boring 80% of maintenance happens without a human thinking about it. Deps stay current. Docs stop lying. Skipped tests get fixed or honestly deleted. You stop opening a repo after three months and finding it's a stranger. The PRs are small and reviewable, which means review is fast — you're not approving a 2,000-line "cleanup" bomb, you're glancing at a one-line version bump.
**What it doesn't get you:** it won't make architectural decisions for you. If your monolith needs to become services, no agent team is going to figure that out from a focus string. It also won't replace good judgment about *when* to take a breaking change. What it does is keep the floor clean so that when you do need to make a big move, you're not wading through six months of drift to get there.
The mental shift is this: stop treating maintenance as the thing you do when everything else is done (it never is), and start treating it as a background process that runs whether or not anyone's watching. That's literally what daemons are for.
## Try it
If you have a repo that's been quietly rotting — and we all do — point a maintenance loop at it for a week and see what comes back. You'll either get a pile of small, useful PRs, or you'll learn something about what "healthy" actually means for that project. Both are wins.
bash
Install and register your first repo
am repo add https://github.com/you/myproject \
--focus "keep docs in sync with code, fix failing tests, bump outdated deps"
am start --local
Repo: https://github.com/javimosch/automaintainer
Hosted: https://automaintainer.intrane.fr
Maintenance isn't going to stop being unpaid labor. But it can stop being invisible labor — and that's most of the battle.
Top comments (0)