A Kanban board with an Ai column between To Do and In Progress, cards flowing from the queue to Done
← Back to Blog
October 2026·Engineering Stories·10 min read

Four AI workers on a Kanban board: 438 merged pull requests in a month

I gave Claude Code its own column on my Gitea boards. A month later four AI workers had opened 465 pull requests and merged 438. The code was rarely the hard part.

For about a month, a large part of the code in my projects has been written by four AI workers. Not a chat window I paste into, and not an autocomplete. Each worker is a small daemon that reads a Kanban board, takes the next card from a column called Ai, works on it in its own container and comes back with a pull request or a question.

The numbers so far: 465 pull requests opened, 438 merged, 319 tasks, roughly 290 hours of agent sessions across three of my own projects, plus a fourth worker on a client project that lives in Jira and Bitbucket. This article is the honest version of how it works, what broke and where the real bottleneck turned out to be.

The setup: a board, a container and a prompt

Everything runs on a self-hosted Gitea instance. Every project has the same board: Backlog, To Do, Ai, Questions, In Progress, Testing and Done. The order of cards in the Ai column is the order of work. If I want the bot to do something, I move the card there. That is the whole interface.

Each project has its own worker:

  • A Linux container per project. The worker, the project checkout, the test database and a headless browser live in an LXD container with its own CPU and memory limits. Nothing the agent installs touches the host or another project.
  • Claude Code as the engine. The daemon starts Claude Code sessions with a base prompt, the task description, the whole comment thread and any attached screenshots. Sessions have ids, so a conversation can be resumed later instead of restarted.
  • The project's own rules. The repository's CLAUDE.md holds the conventions: language of the code, branch naming, required checks before a push, how to run a visual check with test accounts. A small memory directory carries decisions that are not in the code.
  • A deliberately duplicated runner. I copy the runner into each project instead of building a framework. The projects differ in stack, review rules and merge policy, and a copy that knows exactly one project is easier to change than a configurable engine that knows all of them.

The runner itself is plain TypeScript run directly by Node, a few hundred lines per concern: board, sessions, reviews, mentions, merges, alerts, time logging and usage limits.

The work loop: one card at a time

Every few minutes the worker reads the board. If nothing is in progress, it takes the first card from Ai, moves it to In Progress and starts a session. A session ends in one of a few structured outcomes, and each outcome has a defined place on the board:

OutcomeWhat the worker does
Pull requestComments with a summary and the link, moves the card to Testing
QuestionAsks me in a comment, moves the card to Questions; my reply resumes the same session
Usage limitLeaves the card in progress and resumes the same session when the window resets
ErrorOne retry in the same session, then it is treated as a question

Two small rules turned out to matter more than I expected. First, if I move a card from Questions back to Ai without answering, the worker takes its own recommendation as the decision, writes that assumption into the pull request and carries on. That keeps me from becoming a blocking dependency for minor choices. Second, review work beats new work: when I request changes on a pull request, the worker reads the review and inline threads, fixes the branch and answers with a table of comment and change before it picks up anything new.

Anyone can also mention the bot in a task or pull request. It answers in the thread, and if the answer needs code it opens a branch. Mentions never move cards. The board stays the only thing that schedules work.

Who merges what

The worker merges, but only what a human approved. An approved pull request with green CI is merged by the bot, the task is closed and the time is logged. Everything else is a reason to wait, and the worker says which reason in a comment:

  • Conflicts with the base branch: the worker updates the branch and fixes the conflict, at most three times. After the third failed attempt it parks the pull request and asks me instead of looping.
  • Task dependencies: if the task depends on another open task, the pull request waits. This prevents a feature from landing before the foundation it needs.
  • Red CI: no merge, ever. The worker can investigate, but it does not get to declare a failure irrelevant.

Time is logged from evidence, not from session length: the activity on the task's commits and pull requests is grouped into blocks and scaled down by a factor I set, because an agent's wall clock is not an engineer's hour.

When the subscription hits a usage limit, one flag pauses every kind of session. The worker posts a single alert with the expected resume time and keeps doing the cheap work in the meantime: closing tasks and merging pull requests that only waited for green CI.

The numbers after a month

WorkerRunning sincePull requests opened / mergedSessionsSession hoursTasks
SaaS product (Laravel + browser extension)15 September310 / 300375134175
Accounting office portal (Laravel)4 October111 / 95255130101
Logistics project (AdonisJS)7 October44 / 43632643
Client project (Jira and Bitbucket)5 Octoberreviews and branches only102not trackedJira tickets

A few things in that table are more telling than the totals. On the accounting portal, 139 of 255 sessions were review fixes, not new tasks. The agent writes a first version quickly; most of the value, and most of my attention, goes into the rounds after it. The logistics project went from an empty repository to 43 merged pull requests in three days, because its tasks were small, independent and well specified. The portal, full of legal rules and long dependency chains, merged a smaller share of what it opened.

What broke, and almost none of it was the code

I expected the risk to be bad code. The incidents that actually stopped work were operational.

  • The host disk filled up. CI caches, container images and logs grew until builds failed. Nothing merged for a day, and every symptom looked like a different bug.
  • A single DNS resolver. The host had one upstream resolver. When it hiccuped, CI containers could not resolve anything for hours, and pull requests failed for reasons that had nothing to do with their content.
  • An empty secret. A private dependency was installed with a token that was stored as an empty CI secret. Every portal build failed at composer install until I created the missing deploy account.
  • An external outage. One morning GitHub returned 404 for two dependency archives referenced by commit hash. The worker reproduced it on a clean cache, confirmed the code was not the cause and asked me what to do instead of "fixing" the lock file. That was the right call.
  • Too much parallelism. After I moved everything to a bigger server, I gave CI sixteen parallel slots on twelve CPU threads. The load average hit 38, time-sensitive browser tests started failing at random, and two browser suites sharing one Playwright cache broke each other completely. Fewer slots made CI both faster and green.
  • Interrupted sessions count as failures. During the migration I stopped workers in the middle of sessions. One of them was on its third attempt at a conflict, so the interruption parked a perfectly good pull request.

The migration itself was a good test of the setup: a new architecture (ARM to x86), the same Git server, the same workers. Moving a worker meant copying its home directory one to one and rebuilding only what was compiled for the old CPU: native Node modules, the browser binaries and a few tools the agents had assembled for themselves. A scan for ARM binaries before each start caught things I would not have guessed, like a portable database server one agent had unpacked for its tests.

The real bottleneck was me

When I asked why a whole chain of accounting features had not merged for days, the answer was uncomfortable. Seven approved pull requests were waiting on two tasks at the bottom of a dependency chain. One of them had waited six days for my re-review. I had requested a change on a Saturday evening; the worker had implemented it within an hour, explained the legal reasoning in the thread and pushed. Then the pull request sat there, because a requested change only clears when the reviewer looks again.

That pattern repeated. The agents are fast at producing and fixing. What they cannot do is decide what matters, approve their own work or resolve a dependency on a business decision. Every one of those is a human queue, and with four workers that queue fills up faster than I clear it.

Practical changes that helped:

  • A single "start here" task per project where the workers post every alert they cannot solve, instead of comments scattered across fifty threads.
  • Dependency chains that are short and explicit in the tracker. A wrong or stale dependency silently blocks everything above it.
  • Reviewing pull requests at the bottom of a chain first.
  • Reserving my time for decisions and reviews, not for watching sessions.

What I would keep, and what I would do differently

I would keep the board as the only control surface. Moving a card is a decision a human makes; everything after it is mechanics. I would keep the strict merge rule: human approval plus green CI, no exceptions, and a hard limit on automatic retries. I would keep one container and one runner copy per project.

I would build the boring parts earlier: disk and resolver monitoring on the host, CI capacity sized to the machine instead of to my optimism, and a clear report of why each approved pull request is still open. Most of the hours I lost went into finding out why something was not moving, not into fixing it.

The surprising conclusion after 438 merged pull requests is that AI workers do not remove the engineering management work. They concentrate it. The code arrives faster than ever, so the quality of the queue, the tasks, the dependencies and my own review habits decides how much of it reaches production.

If you are planning AI agents that work inside your delivery process rather than next to it, I can help you design the board, the merge rules and the infrastructure around them. See my AI engineering consulting work or contact me.

Igor Gawrys
Igor Gawrys
AI Engineer & IT Consultant · Katowice, Poland