How I work with AI today: DDEV, worktrees and an arena where my agents fight it out

For some tasks, the code I end up merging wasn't written by Opus. It was written by Qwen, a 27B model running on my local server. It doesn't happen often, but it happens, and every time it does it confirms that building this whole contraption made sense.

A while ago I wrote about the five levels of working with AI agents and why I'd stayed at level 3. The system has changed quite a bit since then, so it's time for an update.

First things first: you don't need to build all this

This is how I work today. I know a lot of people will find it very complex, much more than necessary, and I'm not saying everyone should work this way. Some people have more than enough with Claude Code installed on their machine, and that's perfectly fine.

It's what works for me, for my specific case: a freelancer working on several projects at once. I prefer to keep each project separate, always work with DDEV and keep everything as isolated as possible between projects. And I've built it with scaling in mind, so that the day I need to, I can spread it across different machines without rebuilding it. Right now I have two servers at home and I'm already thinking about a third, because I've basically run out of RAM. Even if it looks like a mess from the outside, it's not as hard for me to maintain as it seems.

The three pieces underneath

Before getting into the projects, it's worth being clear about what sits underneath, because they're three different pieces and it's easy to mix them up.

Proxmox is the infrastructure layer. I use it to virtualize my servers: inside each physical machine I have several virtual machines and containers, and each thing lives in its own. It has nothing to do with AI as such, it's simply what lets me split up and isolate everything else.

LiteLLM is an AI proxy. My local inference server is exposed through it as just another API, and third-party APIs go through it too when I need them. That gives me a single endpoint for my applications to connect to, which in practice are mostly the OpenCode containers, and most of the time they end up using my local server. It also gives me cost control and lets me add new models or retire the ones I no longer use without touching each project's configuration.

I've already talked about these two a while back in other articles and in the talk I gave last year at DrupalCamp Spain, so I won't go into how I have them configured.

Atlas is the one that matters most for understanding the rest of this article. It's a task manager I've built specifically for myself, and over time it's become my interface for working. It tracks time, projects and tasks, the time estimates for each project and the AI cost of each task. And it also has the agent workflow built in: Atlas is where worktrees get generated and where harnesses get launched and managed. Deep down it's a meta-harness, what some people are starting to call a code factory. It isn't public and I'm still changing it almost every week, but it shows up in several of the sections that follow, so it's better to introduce it now.

If you're at a mid-sized or large agency, I'd definitely recommend these three. Your own task manager, tailored to how your team works, goes a long way towards helping people work well with agents. LiteLLM or something similar lets you decide who gets access to which models and with what spending limit, in money or in tokens. And if you have your own servers, Proxmox makes managing the whole infrastructure side much easier.

I think more than one agency could benefit from seeing how someone else is setting up their agent orchestration, so from here on I'll go bottom-up, from the base I use in every project to the most experimental part.

The base: everything inside DDEV

All my Drupal projects use ddev-ai-workspace, a public meta add-on I maintain that installs everything I need to work with AI into the project. Claude Code and OpenCode each run in their own container, with access to the project but separate from the web container.

Agents, skills and rules don't live in each project. They come from drupal-ai-agents, my public repository, and they get synced every time I start the project with ddev start. That way I have a unified configuration across all my projects without copying files around.

The interesting part is that I can add more repositories on top. Many of my clients are agencies, and some of them already have their own agents and skills. In those cases I add their repository after mine and the system combines the two: whatever the agency has that I don't gets added, and if they have their own version of something I also have, theirs wins. I get the best of mine plus what the client has already worked on, without having to maintain a different configuration by hand for each one.

There's one part that isn't in the public repository because it's custom-built for me: the connection to Atlas. I put the Atlas API key into the configuration file by hand, and every task sends it its usage stats: which models were used, how many tokens and how much money it cost. It sounds like a detail, but it's what lets me know how much each task really costs me and whether I'm spending more than I should.

One worktree per task

Next up are Git worktrees. I have some scripts that, for each task, create a new worktree of the project with the same DDEV add-ons. They start it up, import a sanitized database of the project, import the configuration and run the deploy. When it finishes, I have a complete environment ready to start working, without having touched anything by hand. And since this workflow is built into Atlas, I launch it straight from the task.

This lets me work on several branches at once. While one agent is on a feature in one worktree, another can be on a bug in another, and I can be reviewing a third. Each with its own database and its own DDEV, without stepping on each other.

Before I had this, the bottleneck was the environment. Switching branches meant reimporting the database, clearing caches and praying the configuration lined up. Now the cost of opening a new task is practically zero.

The arena: let them fight it out

This is the level I'm experimenting with right now. I call it the arena, because in the end it's a colosseum where my AI harnesses fight each other and only one can be left standing: the one that came up with the best-implemented solution. It's published as ddev-ai-arena, although it's still very much in beta and I'm still testing it.

The idea is for complex tasks. The first pass is done entirely by AI, unattended, and several of them do it at the same time.

Here's how it works. First I talk the task through with the orchestrator. We discuss the best way to implement it, it suggests alternatives, I correct what I don't like, and so on until we're clear on how it's going to be done. Once we reach that consensus, the orchestrator runs the worktree scripts I mentioned earlier and creates one worktree per candidate. It passes each one exactly the same prompt. Claude Code runs in one and OpenCode in the other. Later on I want to add Codex.

Each candidate takes as long as it needs. There's no rush, because I'm working on something else in the meantime.

When they've all finished, the orchestrator reviews the code in each branch and picks a winner. But it doesn't throw the others away. Often the losing branch, as a whole, isn't the best option, but it has a function that's better thought out, an edge case the winner didn't consider or a part that's better finished. Those pieces get brought into the winning branch. The end result is a branch with the best of the winner plus whatever the losers did better.

There's an obvious cost. If you have two or three harnesses doing the same task, you're spending two or three times the tokens to end up with a single result. There's no way to sugarcoat that.

In exchange, the quality of the code I get is considerably higher, and I need much less human review. For small tasks it's not worth it. For complex ones, where a bad design decision costs you days later on, it is.

When the small model wins

On my local server I'm currently running Qwen3.8-27B, the dense 27B model Alibaba released in August. I use it from OpenCode, which connects to it through LiteLLM. On the other side, Claude Code uses Opus as its main model and cheaper models for subagents.

Most of the time Claude Code wins, I'm not going to pretend otherwise. But on some specific tasks, the OpenCode branch with Qwen has come out on top, ahead of Opus.

To me, that says a lot. Frontier models aren't always the best for every task, and the only way to find out is to make them compete on real work, not benchmarks. Without the arena I'd never have known, because I'd simply have used Opus for everything. And a model running on my own server winning every now and then is also a way of not depending so much on a single provider.

Harnesses need to dream

There's a concept I saw in a talk that I really liked: agents that dream. I haven't managed to find that presentation again, but I did find this other one from Anthropic, where they introduced their dreaming feature, which is basically about the same thing.

The idea starts from how we work. Sleep is when the brain consolidates what it learned during the day. You can do something equivalent with an AI harness. When it finishes a task, you ask it to go over the problems it ran into and how it solved them, and if it learned something new, to consolidate it into a skill or an agent. The next time it has to do something similar, that knowledge is already in its base and it doesn't have to rediscover it. It does it faster, using fewer tokens, or with better quality without going round in circles.

In the article about provider dependency I already talked about self-improving skills, but I think the name "dreaming" explains it much better. You can do it by hand, asking it to consolidate what it learned when it finishes, or automate it so it happens on its own at the end of each task or once a day.

What your AI learns stays on your computer

There's something about all this self-improvement that almost nobody mentions. By default, everything your AI learns in skills and agents stays on your computer.

If it breaks, gets stolen or you simply work with more than one machine, there's no easy way to recover that knowledge or have it the same everywhere. You've spent months fine-tuning skills and agents, and all of it lives in a local folder you probably don't even have a backup of.

That's exactly why my system works with repositories. When the knowledge base gets updated, it's pushed to the corresponding repository. And all the Claude Code and OpenCode containers pull from those repositories every time they start. That way all my harnesses and all my projects always have the latest consolidated knowledge, whether from the public repository or from a private one, because some projects have their own knowledge base that can't be public.

Audit so the AI corrects itself

In the skills I've told the AI to take into account performance, security, community best practices, PHPCS, PHPStan, code duplication and complexity. And so that doesn't stay a good intention, I use the Audit module.

Every Drupal site I'm involved in has Audit installed, at least locally. The harness can run the Drush commands to check the scores and see whether anything got worse with the code it just generated. If it sees something that doesn't follow recommended practices, it fixes it before saying it's done, and before I go in to review anything. I explained it in more detail in why green tests aren't enough.

A browser for the agents

The other feedback piece is Playwright. I have a container in DDEV with a headless browser and its MCP server, with visibility into the worktree being worked on.

With that, agents can browse the site locally, click around, fill in forms and check how the user interaction behaves. They can also take screenshots at different screen sizes, which is especially useful when you're building frontend. It takes a screenshot before the change, modifies the code, takes another one afterwards and compares. If the page throws an error or something has moved out of place, it notices and fixes it.

Without this, an agent touching frontend is working blind. It writes CSS and Twig and assumes it looks fine. With a browser, the loop gets closed.

It's nothing new. People have been working this way for over a year. But I'm still surprised by how many people don't even know you can connect an agent to a browser so it can see what it's doing.

That said, all the browser interaction runs on dumber, cheaper models. Clicking a button and taking a screenshot doesn't need Opus or Fable, and using them for that is throwing money away. I've already explained how I organize subagents by model so my subscription or API budget lasts as long as possible, and this is one of the places where it shows the most.

This will be outdated soon

All of this is how I work right now, in early November. And with how fast everything changes, it'll most likely be outdated by the end of the year. Every few weeks there's a new model, a new feature in the harnesses or a different way of organizing agents, and I try to stay on the crest of the wave as far as I can.

The part that changes the most is Atlas. I keep refining it every week, sometimes every day, and that's why I haven't written about it in detail yet. But I think that's where we're all heading: a code factory, with an orchestrator on top managing everything I've described in this article, from worktrees to the arena, and leaving it up to you to decide what gets done and to review what comes out.

Every step I take goes in that direction. Making the base a bit more solid so I can build things on top that save me time and money. And if something costs me less to implement, I can also deliver it to my clients sooner.

Before it was a ticket. Now it's a prompt.

Does your Drupal leak?

Tell me what you are working on and what worries you. I'll reply personally and, if I'm not the right person, I'll tell you too.