← Writing

The Bottleneck Isn't the Agent. It's the Environment.

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 was isolated. Everything else was shared.

Tools like Conductor make spinning up parallel agents in isolated workspaces easy. It’s slick. But after using Conductor, and after building my own multi-workstream setup, I think the conversation about parallel agents is missing something fundamental.

Most of the tooling solves code isolation. 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?

What makes an agent productive isn’t just the ability to read and write files. It’s the ability to verify what it did. 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. That’s a very different thing from an agent that 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

One produces code that looks right. The other produces code that works. Getting the second version 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.

This isn’t glamorous work. It’s plumbing. But it’s the plumbing that 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. Not because of technical limits, but because I still want to be 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, course-correct when needed. More than that and you’re not supervising, you’re just generating pull requests you’ll have to review later anyway.

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.

But for any application with stateful services, the workstream is the branch plus its running environment. Code isolation without environment isolation is like giving someone an office with a desk but no computer. The space exists. The ability to do the work doesn’t.

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