Building an AI Software Factory on Cursor Cloud Agents

Cursor recently told me I'm in the top 1% of its users. You'd assume that means I write a lot of code. Mostly, I don't. Up to ten Cloud agents write it for me, and I decide what they work on.

It started with a simple worry. An agent running on my laptop has access to my files, my credentials and my shell. When it gets something wrong, it gets it wrong on my machine. So I decided to move all agent work off my computer and into the cloud. That quickly grew into a multi-agent workflow, where Cursor agents take my ideas all the way from a ticket to a pull request that's ready to merge.

I call it the fleet: a small AI software factory built on Cursor Cloud agents. My tickets live on a GitHub Project board. A supervisor, a Cursor automation, checks that board every few minutes and hands ready tickets to worker agents. Each worker turns its ticket into a pull request, and I review and merge what comes back.

The fleet loop · supervisor, worker, PR, bundled follow-ups, one human
The fleet loop · supervisor, worker, PR, bundled follow-ups, one human

The board

The first version of the fleet ran on Beads, which stores its tickets as a database inside Git. It served me well for a while. Slack was my only interface into that first version. Beads is a CLI, so I wired it up to Slack and created, triaged and refined tickets there. It worked, but as more agents got involved, it stopped being the right fit for how I wanted to work.

I needed a board I could see, edit and drag things around on. So I took everything I'd learned and rebuilt the fleet from scratch on GitHub Issues and Projects. The board has four columns: Backlog, Ready, In Progress and Done.

The Backlog holds everything I'm thinking about, from loose notes to fully refined tickets. When I'm happy with a ticket, I drag it into Ready. That's my signal to the fleet that the ticket has everything it needs.

In Progress has a fixed capacity of 10 concurrent agents, which is the size of the fleet. Ready works as a queue. Whenever a slot frees up, the next ticket moves into In Progress. That way I can fill Ready as much as I like, and the fleet never takes on more than it can handle. Or, let's be honest, more than I can handle.

Why 10? Without a cap, the fleet could flood a repo with hundreds of PRs, and every merge would set off a wave of rebases across all of them. 10 turned out to be a good balance for the repos I'm working on right now.

A ticket lands in Done once I merge its PR.

How it works

The supervisor is a Cursor automation that runs every few minutes, and each run is one tick. A tick has two jobs: start new work, and look after work that's already running. Let's follow a single ticket.

The supervisor counts the tickets in In Progress. If all 10 slots are taken, there's nothing to start. If a slot is free, it takes the next ticket from Ready, moves it to In Progress and launches an agent for it. That agent becomes the ticket's worker. It gets two things: a general worker prompt, and the issue body describing the problem.

Then the supervisor simply exits. No babysitting.

From here the worker owns the task. It runs in its own cloud environment, clones the repo and works through the problem. Its most important rule is to stay in scope: anything unrelated becomes a suggested ticket for me to review, not a fix. I'll go into how that works in a follow-up post about how the fleet and I communicate. When the worker is done, it opens a pull request.

To keep track of all this, every issue ends with a small JSON metadata block. It holds the agent's ID and URL, the PR link, the last computed change hash (more on this in a second) and a nudge count.

{
  "worker_id": "bc-4432caf8-d04e-452e-9b5c-95033a7b5d61",
  "worker_url": "https://cursor.com/agents/bc-4432caf8-d04e-452e-9b5c-95033a7b5d61",
  "pr_url": "https://github.com/mega-corp/billing-service/pull/168",
  "branch": "cursor/168-fix-invoice-rounding-8ecc",
  "followup_state": "90b1f1ed9fde0c0c",
  "nudge_count": 1
}

That means the supervisor needs no state of its own. On every tick it reads the ticket and knows which agent and PR belong to it, what the PR looked like last time, and how often it has already nudged the worker.

Driving PRs to completion

On each tick the supervisor checks every In Progress ticket. If the worker is still busy, it leaves it alone. If the worker has finished but there's no PR, it nudges the worker to open one. If there's a PR, it checks the PR's status: review comments, CI checks and whether the branch has fallen behind main.

The tricky part is knowing what's new. On every tick, the supervisor sees the whole PR again, including comments and failed checks the worker has already dealt with. If it nudged the worker about all of that on every tick, the worker would wake up every few minutes to feedback it has already handled. So the supervisor computes a hash over the PR's status and saves it in the ticket. On the next tick, it only steps in if the hash has changed.

When the hash does change, it's often for more than one reason: a new review comment, a failed check, main moving ahead. The supervisor bundles everything that changed into a single follow-up, like "you're behind main, and there's a new review comment on this line," and nudges the worker once. It always goes to the same worker that started the job, so it still has all its context.

That leaves my part, which is small but important. When I review a PR, I just leave comments, and the worker picks them up on the next tick. The same goes for comments from automated reviewers and from anyone else who looks at the PR. When the PR is green, I'm the one who approves and merges it.

Drawbacks

The biggest drawback is that merging is still manual. Every PR waits for me to approve and merge it. That's on purpose for now, but it makes me the bottleneck: the fleet can produce work faster than I can review it. This is where the cap of 10 really helps. A ticket stays In Progress until its issue is closed, so there are never more than 10 PRs waiting for me, and the fleet slows down to my pace instead of piling up work.

A smaller one: agents can occasionally go round in circles, fixing one thing and breaking another. Every follow-up raises the nudge count, and after 7 rounds the supervisor stops and waits for me to reset it. In practice that doesn't happen often.

Unexpected bonus

Moving to the cloud was about safety, but it gave me something else too. Every piece of work lives in a GitHub issue, and every issue links to the agent session behind it. Without the cloud, those sessions would sit on someone's laptop, and the ticket would be the only thing teammates could share.

Now the fleet's whole history can be searched, by me and by agents. I mostly use that to improve the fleet itself.

Here’s a real example. New tickets don’t only come from me. Workers also suggest tickets when they find something out of scope. So before the fleet creates a ticket, it checks whether one already exists. Still, one day I found two tickets for the same work. I asked the fleet why, and it searched its own history for the answer: the duplicate check only ran for tickets created by workers, not for the ones I requested through Slack. One question was enough to find the gap, and closing it was a small change to the rules.

Wrapping up

So that's the story behind the top 1%. I don't type faster than anyone else. Whenever there's work on the board, up to ten agents pick it up, and I decide what gets merged.

This post covered the core loop, but the fleet does more. In the next post I'll look at how the fleet and I communicate: how work gets in through Slack, and how workers ask for help when they're stuck or suggest new tickets.