The Dark Factory
Working with my agent runs smoothly – and yet I feel blocked, because during the implementation I'm just watching. How the wish for more efficiency turned into a factory that writes its own story on the merge request.
A Spectator at My Own Machine
By now the collaboration with my agent runs really well. The workflow has settled in:
- Claude and I discuss an idea and formulate the plan.
- He implements it.
- I push the whole thing into a merge request.
- I have Claude do a review and, if needed, fix it right away – and yes, it really does help when Claude reviews Claude.
- Merge.
A good approach. Only: in step 1 I actively contribute. For the rest I’m a spectator at my own machine. The implementation sometimes takes 30 minutes or more; I wait, to make a few clicks now and then – and the better we gel as a team, the fewer questions I even have to answer after the planning. The steps after the implementation are dull too: wait for the pipeline, have Claude review and fix, wait for the pipeline again – yawn. The exciting steps – the idea, hashing out the approach – those I want to keep. The dull sitting-alongside can happily go.
So the obvious question: can the “doing” not run somewhere else while I turn to the next idea?
Evolution of the Idea
I could have solved this with Claude in the cloud, and in principle you can also just start several sessions locally. But at my company Dark Factory is currently a topic – the hall where the machines keep running even when nobody turns on the light – and I simply felt like building something in that direction.
I assumed I’d need some kind of AI server for it, and had already started looking for n8n hosting. Claude’s recommendation was far simpler: we already have a factory hall. It’s called GitLab CI.
The handover point stays the creative one: out of the discussion comes a GitLab work item with an implementation plan. From there the hall takes over – as perfectly ordinary pipeline jobs:
- implement takes the plan from the ticket, has Claude implement it, commits, pushes a branch and opens the merge request. So that the implementer knows not just the how but also the why, the job loads, alongside the task’s plan, the spec of the parent story.
- review kicks in as soon as the tests are green, and attaches its findings to the MR.
implement job: from the merge request on, the perfectly normal pipeline runs – unchanged.At the end there’s an MR that a human signs off on. Me. No more watching the typing; the judgment stays.
Evolution of the Implementation
The start was surprisingly easy:
- an image with the Claude CLI and the dev tools (Maven, Node, …)
- a new job
- a clever prompt
- a few secrets in GitLab – fortunately, unlike Codex, Claude offers the option of creating a fixed token for a job, to use its quota in the cloud too
Done!
The review job differs essentially only in the prompt. But even though Claude-reviewing-Claude delivers useful findings, I wanted to use a different model to shrink the blind spots. So: opencode into the image, set up a DeepSeek account, put in $20 – and DeepSeek has been reviewing ever since. I didn’t do extensive A/B testing of the two models. Quantitatively DeepSeek finds more than Claude did before; as the implementer, Claude accepts them most of the time. Qualitatively they’re usually small things. But often enough the review also catches bigger issues (doesn’t work under this or that condition; this database access needlessly results in N+1 queries), so I definitely want to keep it.
It works!
I won’t go into the technical problems in detail, but there were a few of those too:
- 30 minutes of implement drain GitLab’s 400-minute quota fast → a dedicated runner had to happen.
- Learning: whatever you can make deterministic, you should make deterministic. Instead of, in the prompt, “look at the MR, implement this, commit, push, create an MR,” better: a script loads the task and builds the prompt from it, Claude implements, the script commits, pushes and creates the MR → fewer surprises.
- Seeing Claude’s internal thoughts afterwards – or, even better, while he’s working – is extremely helpful. Why did he take so long for this little task? Is he still coding, or already stuck?
- Over time the handling around the agent call got so complex that I switched from Bash to Node.
The Storyteller
One thing still bugged me conceptually, though: I was still doing the fixing of the review findings locally. By then I had a dedicated skill for it, which I only tell: “take a look at the MR, give an assessment of what should be done, ask for the go-ahead and fix it on its own working tree, commit and push.” I want to keep control over what gets fixed and how many feedback loops we run. If this part is to move to the cloud too, we need a communication channel.
The good thing: we didn’t have to invent anything new for it. MRs are meant exactly for implementer and reviewer to communicate – whether those are humans or agents doesn’t matter. The necessary change was small: where the reviewer previously wrote its review into one big comment, it now opens a separate thread per finding. I write on the threads whether something should be fixed or ignored, and add further notes where needed. If I comment nothing, by convention it means: the fix job decides whether it’s relevant or not. What happened in the fix gets appended to the thread.
The fixer is essentially the implementer with an adjusted prompt. Originally I even wanted to reuse the same session, but that would have made the persistence business in the pipeline needlessly complicated. Instead the implementer produces a handover file in which it summarizes what it did and why – and above all, if there were problems that kept it from following the plan. That goes as the first comment on the MR.
implement) and the fix are added – in between runs the familiar MR pipeline. After the review it's merge or fix; a fix is just one more commit.The reviewer now gets this information too. So it has more context on the implementation, can improve its review accordingly, and no longer lists, run after run, the findings I discarded long ago.
Not So Dark After All
The nicest part is something I hadn’t planned at all. Because all the processes talk via the MR, a wonderfully traceable story emerges there all by itself: at the top the description of what was implemented, with implementation notes. Below it the threads of the foreign reviewer, each a cleanly delimited finding. Next to them, where I triggered it, the answers of the fix run. Whoever opens the MR doesn’t read the result of a black box, but can follow step by step how this code came out of a ticket.
Finally, I told the implement job to attach screenshots of the relevant UI parts to the MR for UI changes. That way I can check right away whether I like the result – or send new stage directions to the fixer via a review comment.
My goal was to build a hall where the machines keep running when I turn off the light. What came out is one that, in broad daylight, tells me a story about the implementation – far more detailed and colorful than much of what I’ve seen at the manual workbenches.
A Look Inside the Hall
Here’s what such a story looks like: three excerpts from a real MR (in German) – the time view in the program planner, implemented by implement, reviewed by DeepSeek, merged by me. Click an image to open it at full size.
implement job attached of the new UI – before reading a single line of code, I can see whether I like the result.
That “fixed in …” brought back a memory. Back when I worked in the pharmaceutical industry and we switched to Git, Gitflow and pull requests – which wasn’t nearly as long ago as you might think – our process owners had one wish: for traceability, every fix of a review remark should state the commit it was fixed in. Back then we managed to argue it away as technically infeasible and far too much manual upkeep. Here I got it as a gift, without even asking.