← Writing

Running parallel coding agents is an environment problem

Last month I wanted to run two Claude Code sessions in parallel, each working on a different feature. The first thing I had to deal with:

Error: listen EADDRINUSE :::8000

Both agents were trying to bind to the same port, connect to the same PostgreSQL database, and route through the same Docker Compose project. The code sat in separate folders, but everything else around it was shared.

Tools like Conductor make spinning up parallel agents in isolated workspaces pretty slick. But after using Conductor for a while and then building my own multi-workstream setup, I kept coming back to the same thought: most of this tooling is solving code isolation, and in my experience code isolation is the easy part.

Why not just use worktrees?

A git worktree gives you a separate copy of the source tree on a separate branch. That’s it. No separate database. No separate API server. No separate frontend. No separate ports.

If your project is a pure library, no services, no state, no network, worktrees are all you need. But my codebase isn’t a library. It has a PostgreSQL database, a backend API on port 8000, a frontend dev server on port 5173, Redis, and a docker-compose file tying it all together. Most real applications look more like this than like a library.

I ended up not using worktrees at all. I just copy the folder. There are always dotfiles, local configs, .env files, things that aren’t in version control but that the app needs to run. With worktrees you still end up copying those over manually. At that point, the advantage worktrees give you over a plain folder copy is minimal, and you get the same result with less ceremony.

At one point, an agent in one copy ran a migration that broke the other copy, since they were sharing the same database. That’s the kind of problem code isolation doesn’t solve.

Why does feedback matter more than code access?

An agent is only as productive as its ability to verify what it just did. Reading and writing files doesn’t get you very far on its own. Can it run the tests? Can it start the server and hit an endpoint? Can it see the actual error in the logs?

An agent working with no running environment is coding blind. It can read files, write files, and hope. You get something very different once the agent can:

  • Connect to its own database and see the actual data
  • Hit the API and get a real response
  • Open the frontend with Playwright and verify the UI
  • Read the server logs when something breaks
  • Run E2E tests against a fully isolated stack

The first kind of agent writes code that looks plausible; the second actually produces something that works, and getting there meant solving a problem none of the off-the-shelf tools address.

What I ended up building

I built a multi-workstream local stack that gives each copy of the repo its own isolated environment:

  • Database: a dedicated PostgreSQL instance, not a shared one
  • Ports: deterministic, collision-free API and frontend ports derived from the directory name. If the copy lives in project-3/, it gets port 8003 and 5176.
  • Docker Compose project: isolated containers that don’t interfere with other copies
  • Hostnames: *.localhost per copy for clean routing
  • Environment resolution: a single stack-env script that computes all of this, with guards that reject cross-contaminated env vars

Two modes: working from the main checkout, everything defaults to the usual localhost:8000 setup. Copy the repo into a different directory and it auto-detects multi-workstream mode, assigning unique ports and a unique database. No manual configuration.

Right now I run the server and frontend locally, with only Postgres and Redis in Docker Compose. I think the next step is moving the server and frontend into Docker Compose too, so each copy gets a fully containerized stack with no port collisions at all.

None of this is glamorous, it’s mostly plumbing, but that plumbing is what turns an agent from a code generator into something that can actually verify what it builds.

How many workstreams actually work?

With the environment problem solved, the next question was how many parallel agents I could actually supervise.

I landed on four or five. There’s no technical limit there, I just want to stay in the loop. I don’t think we’re at the point where you fire off an agent and fully trust whatever comes back. Maybe we won’t be reviewing code line-by-line for much longer, but I still want to watch what the agent is doing. Sometimes I need to step in and redirect. Sometimes the agent is heading down a path that technically works but is wrong for the codebase. Sometimes it needs a piece of context that isn’t in the code.

Four or five means I can keep a tab on each one, check in, read the approach, and course-correct when needed. Past that I’m not really supervising anymore, I’m just queuing up pull requests to review later.

Where does this leave us?

The current generation of parallel agent tools, Conductor, raw worktrees, Claude Code’s built-in worktree support, all operate at the code layer. They assume the workstream is the branch.

For any application with stateful services, though, the workstream is the branch plus its running environment. Code isolation without environment isolation is a bit like giving someone a desk with no computer; the space is there, but they still can’t do the work.

I started that first parallel session knowing code isolation wasn’t going to be the hard part, and it wasn’t. The hard part was everything around the code: the ports, the databases, the ability to run a test and get a real answer back. I think that’s where the leverage is for whatever comes next.