Keboola Connection still has part of the system running on Zend Framework 1, the rest is already in Symfony. We're trying to cut that old part out, as much and as fast as we can. A short prompt was enough: in about 22 hours, 13 pull requests were merged. Three more are open. And it can keep going for as long as any Zend remains in Connection.
A factory with the lights off
A dark factory, sometimes called a lights-out factory, is a manufacturing term for production that can run with almost no people on the shop floor. The lights can stay off because nobody has to stand at the line. Machines take in material, they manufacture, and a finished piece comes out the other end. A person built the line first, set the rules, and then shows up mainly when something needs maintenance, a quality check, or an intervention. Very few factories actually run like this around the clock with no people at all. As an idea, though, it fits.
And that's exactly what this experiment is about. Not whether an agent can remove Zend in one shot. Whether a solid harness can give you a line that runs on its own. Once you have that, a short prompt is enough.
All I had to write was this
Your goal is to get as much of the Zend out of Connection as you can. You have until Friday.
Work in worktrees, use the skills in the repo for removing Zend, and don't open too many PRs at once, so we can keep up with merging them.
That's the whole brief. No long plan, no drip-feeding tasks from my side. The goal and the deadline: as much Zend gone as possible by Friday. The rest is guardrails. Each agent in its own worktree, so they don't step on each other. Use the skills that are already in the repo. And don't flood us with PRs, because we still have to get them merged.
The prompt doesn't say how a task is taken all the way to merge, that every PR should be tested with end-to-end tests, how merging to master works, what still needs to be cut away, or which gate is satisfied by a bot approval and which one wants a human. All of that is already in the setup. That's why two sentences are enough. Without what's underneath, it would just be a bold message in a chat window.
The map matters more than the skill
The last piece in there is a skill called migrate-highway. The skill itself isn't all that important. What matters is the map that comes with it.
It says which files in the modern part of the application still depend on the old one. What has to be cut out, rewritten, reformatted, have constants converted, and so on. I generated the map once with help from AI. Now AI rewrites it and a script checks it. The agent doesn't have to rediscover it at the start. It has a map, picks the next piece from it, and moves on.
I removed 56 dependencies from the new codebase on the old one.
From Linear to merge
Around that map we still have the same skills as in the setup. ship-task takes a task from Linear and gets it to just before merge. The gates are described in the skills. Some things can be merged on a bot approval alone. Some need a human review.
So it isn't just "here's a file, rewrite it". The agent picks up the work from Linear and sees it through. Every PR follows the same path: draft, E2E, the ai-review label, switching to ready, and joining the merge queue. One agent per git worktree.
On top of that we have skills for E2E tests, watching PRs and review threads, pre-commit checks, and endpoint migration.
When the gates say a bot is enough, it can go on without a human. When they say a human review is needed, it waits. Once a person approves, it merges automatically. The human hasn't left the line. They still approve what human review requires. They just don't have to stand at every step and push the work forward by hand.
BTW, we didn't build this harness for this one experiment. I described what the setup looks like for us in Everyone has better models. Why do some teams fly while others don't?. Here I just pointed it at one clear goal and let it run.
13 PRs in 22 hours
The main Claude Code session is the organizer. It starts subagents, they take care of the specific tasks, and it only checks whether they met all the conditions for a merge to master. And they go, and go, and go, until the goal is met.
I just tried it. In about 22 hours, 13 PRs were merged, three are open, 16 in total. One of the open ones is already in the merge queue, two are waiting for approval, and one of those still has E2E running after a rebase. Nothing is stuck.
The median from open to merge is about two hours. The fastest PR was done in 57 minutes. The ones that went out overnight waited 10 to 13 hours for review. One PR took 5.7 hours because of a long E2E queue and a bug fix.
The merged changes were small. Roles, quoting, admin features, a component detail, a table profile, a metadata fix, a metadata contract, a UI app detail, an event application, decomposing a string ID, dead Zend views, a table metadata list, constants for a file storage provider.
Two things slow it down the most: the E2E queue and waiting for human approvals. Single-suite runs line up in the queue with other runs, and the wait is typically 30 to 90 minutes. On some PRs the reviewer bot comes back with needs_human. And once a review is sitting with a person, the agent does not ask again, those are the rules.
Twice it kicked off E2E before the PR image was built, and once with the wrong filter. It fixed that itself. One mistake: it picked a candidate from a stale local main, and that piece was already done in a merged PR. All that was lost was the agent's time. Since then it picks candidates from origin/master.
Those 70 remaining actions won't get done without a human, that's true. But the Zend dependencies are going down, and the line keeps running overnight. Pretty cool.